Skip to content

Commit fb4e622

Browse files
add error handling
1 parent 8ccc467 commit fb4e622

File tree

5 files changed

+825
-77
lines changed

5 files changed

+825
-77
lines changed

src/launchpad/constants.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,62 @@
11
"""Constants used throughout the Launchpad application."""
22

3+
from enum import Enum
4+
35
# Kafka topic names
46
PREPROD_ARTIFACT_EVENTS_TOPIC = "preprod-artifact-events"
7+
8+
# Error code constants (matching the Django model)
9+
ERROR_CODE_UNKNOWN = 0
10+
ERROR_CODE_UPLOAD_TIMEOUT = 1
11+
ERROR_CODE_ARTIFACT_PROCESSING_TIMEOUT = 2
12+
ERROR_CODE_ARTIFACT_PROCESSING_ERROR = 3
13+
14+
# Artifact type constants
15+
ARTIFACT_TYPE_XCARCHIVE = 0
16+
ARTIFACT_TYPE_AAB = 1
17+
ARTIFACT_TYPE_APK = 2
18+
19+
# Retry configuration
20+
MAX_RETRY_ATTEMPTS = 3
21+
22+
# Health check threshold - consider unhealthy if file not touched in 60 seconds
23+
HEALTHCHECK_MAX_AGE_SECONDS = 60.0
24+
25+
26+
class OperationName(Enum):
27+
"""Enum for operation names used in retry logic."""
28+
29+
PREPROCESSING = "preprocessing"
30+
SIZE_ANALYSIS = "size analysis"
31+
32+
33+
class ProcessingErrorMessage(Enum):
34+
"""Fixed set of error messages for artifact processing."""
35+
36+
# Network-related errors
37+
DOWNLOAD_FAILED = "Failed to download artifact from Sentry"
38+
UPLOAD_FAILED = "Failed to upload analysis results to Sentry"
39+
UPDATE_FAILED = "Failed to update artifact info in Sentry"
40+
41+
# Processing-related errors
42+
PREPROCESSING_FAILED = "Failed to extract basic app information"
43+
SIZE_ANALYSIS_FAILED = "Failed to perform size analysis"
44+
ARTIFACT_PARSING_FAILED = "Failed to parse artifact file"
45+
UNSUPPORTED_ARTIFACT_TYPE = "Unsupported artifact type"
46+
47+
# System-related errors
48+
TEMP_FILE_CREATION_FAILED = "Failed to create temporary file"
49+
CLEANUP_FAILED = "Failed to clean up temporary files"
50+
51+
# Timeout errors
52+
PROCESSING_TIMEOUT = "Processing timed out"
53+
54+
# Unknown errors
55+
UNKNOWN_ERROR = "An unknown error occurred"
56+
57+
58+
# Operation to error message mapping
59+
OPERATION_ERRORS = {
60+
OperationName.PREPROCESSING: ProcessingErrorMessage.PREPROCESSING_FAILED,
61+
OperationName.SIZE_ANALYSIS: ProcessingErrorMessage.SIZE_ANALYSIS_FAILED,
62+
}

src/launchpad/sentry_client.py

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,31 @@
1515

1616
import requests
1717

18+
from requests.adapters import HTTPAdapter
19+
from urllib3.util.retry import Retry
20+
1821
logger = logging.getLogger(__name__)
1922

2023

24+
def create_retry_session(max_retries: int = 3) -> requests.Session:
25+
"""Create a requests session with retry configuration."""
26+
session = requests.Session()
27+
28+
retry_strategy = Retry(
29+
total=max_retries,
30+
backoff_factor=0.1,
31+
status_forcelist=[429, 500, 502, 503, 504], # Retry on these HTTP status codes
32+
allowed_methods=["HEAD", "GET", "OPTIONS", "POST", "PUT", "DELETE"],
33+
raise_on_status=False, # Don't raise on HTTP errors, let our code handle them
34+
)
35+
36+
adapter = HTTPAdapter(max_retries=retry_strategy)
37+
session.mount("http://", adapter)
38+
session.mount("https://", adapter)
39+
40+
return session
41+
42+
2143
class SentryClient:
2244
"""Client for authenticated API calls to the Sentry monolith."""
2345

@@ -27,36 +49,34 @@ def __init__(self, base_url: str) -> None:
2749
if not self.shared_secret:
2850
raise RuntimeError("LAUNCHPAD_RPC_SHARED_SECRET must be provided or set as environment variable")
2951

52+
self.session = create_retry_session()
53+
3054
def download_artifact(self, org: str, project: str, artifact_id: str) -> Dict[str, Any]:
3155
"""Download preprod artifact."""
3256
endpoint = f"/api/0/internal/{org}/{project}/files/preprodartifacts/{artifact_id}/"
3357
url = self._build_url(endpoint)
3458

35-
try:
36-
logger.debug(f"GET {url}")
37-
response = requests.get(url, headers=self._get_auth_headers(), timeout=120, stream=True)
38-
39-
if response.status_code != 200:
40-
return self._handle_error_response(response, "Download")
41-
42-
# Read content with size limit
43-
content = b""
44-
for chunk in response.iter_content(chunk_size=8192):
45-
if chunk:
46-
content += chunk
47-
if len(content) > 5 * 1024 * 1024 * 1024: # 5GB limit
48-
logger.warning("Download truncated at 5GB")
49-
break
50-
51-
return {
52-
"success": True,
53-
"file_content": content,
54-
"file_size_bytes": len(content),
55-
"headers": dict(response.headers),
56-
}
57-
except Exception as e:
58-
logger.error(f"Download failed: {e}")
59-
return {"error": str(e)}
59+
logger.debug(f"GET {url}")
60+
response = self.session.get(url, headers=self._get_auth_headers(), timeout=120, stream=True)
61+
62+
if response.status_code != 200:
63+
return self._handle_error_response(response, "Download")
64+
65+
# Read content with size limit
66+
content = b""
67+
for chunk in response.iter_content(chunk_size=8192):
68+
if chunk:
69+
content += chunk
70+
if len(content) > 5 * 1024 * 1024 * 1024: # 5GB limit
71+
logger.warning("Download truncated at 5GB")
72+
break
73+
74+
return {
75+
"success": True,
76+
"file_content": content,
77+
"file_size_bytes": len(content),
78+
"headers": dict(response.headers),
79+
}
6080

6181
def update_artifact(self, org: str, project: str, artifact_id: str, data: Dict[str, Any]) -> Dict[str, Any]:
6282
"""Update preprod artifact."""
@@ -174,7 +194,7 @@ def _make_json_request(
174194
operation = operation or f"{method} {endpoint}"
175195

176196
logger.debug(f"{method} {url}")
177-
response = requests.request(
197+
response = self.session.request(
178198
method=method,
179199
url=url,
180200
data=body or None,
@@ -249,7 +269,7 @@ def _upload_chunk(self, org: str, chunk: Dict[str, Any]) -> bool:
249269
}
250270

251271
try:
252-
response = requests.post(url, data=body, headers=headers, timeout=60)
272+
response = self.session.post(url, data=body, headers=headers, timeout=60)
253273

254274
success = response.status_code in [200, 201, 409] # 409 = already exists
255275
if not success:

0 commit comments

Comments
 (0)