Skip to content

Commit 4d15821

Browse files
authored
fix(sse_stream_access_logs): Add timeout (#6198)
1 parent db16264 commit 4d15821

File tree

2 files changed

+70
-1
lines changed

2 files changed

+70
-1
lines changed

api/sse/sse_service.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import csv
22
import logging
3+
import time
34
from functools import wraps
45
from io import StringIO
56
from typing import Generator
@@ -57,11 +58,24 @@ def send_environment_update_message_for_environment(environment): # type: ignor
5758
)
5859

5960

60-
def stream_access_logs() -> Generator[SSEAccessLogs, None, None]:
61+
def stream_access_logs(
62+
timeout_seconds: int = 300,
63+
) -> Generator[SSEAccessLogs, None, None]:
6164
gpg = gnupg.GPG(gnupghome=GNUPG_HOME)
6265
bucket = boto3.resource("s3").Bucket(settings.AWS_SSE_LOGS_BUCKET_NAME)
6366

67+
start_time = time.time()
68+
6469
for log_file in bucket.objects.all():
70+
# Check if timeout has been reached before processing each file
71+
elapsed_time = time.time() - start_time
72+
if elapsed_time >= timeout_seconds:
73+
logger.warning(
74+
"stream_access_logs timeout reached after %.2f seconds, stopping log processing",
75+
elapsed_time,
76+
)
77+
return
78+
6579
try:
6680
encrypted_body = log_file.get()["Body"].read()
6781
except ClientError as e:

api/tests/unit/sse/test_sse_service.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,3 +223,58 @@ def test_stream_access_logs_reraises_non_nosuchkey_errors(
223223
list(stream_access_logs())
224224

225225
assert exc_info.value.response["Error"]["Code"] == "AccessDenied"
226+
227+
228+
def test_stream_access_logs_respects_timeout(
229+
mocker: MockerFixture, aws_credentials: None
230+
) -> None:
231+
# Given - Mock bucket with multiple objects
232+
mock_obj_1 = mocker.MagicMock()
233+
mock_obj_1.key = "file1"
234+
mock_obj_1.get.return_value = {"Body": mocker.MagicMock(read=lambda: b"data1")}
235+
236+
mock_obj_2 = mocker.MagicMock()
237+
mock_obj_2.key = "file2"
238+
mock_obj_2.get.return_value = {"Body": mocker.MagicMock(read=lambda: b"data2")}
239+
240+
mock_obj_3 = mocker.MagicMock()
241+
mock_obj_3.key = "file3"
242+
mock_obj_3.get.return_value = {"Body": mocker.MagicMock(read=lambda: b"data3")}
243+
244+
mock_bucket = mocker.MagicMock()
245+
mock_bucket.objects.all.return_value = [mock_obj_1, mock_obj_2, mock_obj_3]
246+
247+
mocker.patch(
248+
"sse.sse_service.boto3.resource"
249+
).return_value.Bucket.return_value = mock_bucket
250+
251+
# Mock GPG to return valid log data
252+
mocker.patch(
253+
"sse.sse_service.gnupg.GPG"
254+
).return_value.decrypt.return_value = mocker.MagicMock(
255+
data=b"2023-11-27T06:42:47+0000,test_key"
256+
)
257+
258+
# Mock time.time() to simulate timeout after processing first file
259+
mock_time = mocker.patch("sse.sse_service.time.time")
260+
mock_time.side_effect = [
261+
0, # start_time
262+
0, # first check (before file 1) - elapsed = 0
263+
10, # second check (before file 2) - elapsed = 10 (exceeds timeout)
264+
]
265+
266+
mocked_logger = mocker.patch("sse.sse_service.logger")
267+
268+
# When - Stream with a 5 second timeout
269+
logs = list(stream_access_logs(timeout_seconds=5))
270+
271+
# Then - Should only process first file and stop at timeout
272+
assert len(logs) == 1
273+
mock_obj_1.delete.assert_called_once()
274+
mock_obj_2.delete.assert_not_called()
275+
mock_obj_3.delete.assert_not_called()
276+
277+
mocked_logger.warning.assert_called_once_with(
278+
"stream_access_logs timeout reached after %.2f seconds, stopping log processing",
279+
10,
280+
)

0 commit comments

Comments
 (0)