|
16 | 16 | op, |
17 | 17 | sensor, |
18 | 18 | ) |
19 | | -from dagster_k8s import execute_k8s_job |
20 | 19 | from pydantic import BaseModel, ValidationError |
21 | 20 |
|
22 | 21 | from moderate.enums import Variables |
23 | 22 | from moderate.matrix_profile.log_utils import ( |
| 23 | + execute_k8s_job_with_log_capture, |
24 | 24 | extract_error_summary, |
25 | | - get_current_namespace, |
26 | | - parse_job_name_from_exception, |
27 | | - retrieve_pod_logs, |
28 | 25 | upload_logs_to_s3, |
29 | 26 | ) |
30 | 27 | from moderate.resources import ( |
@@ -80,52 +77,48 @@ class MatrixProfileJobResult: |
80 | 77 |
|
81 | 78 | def handle_job_failure( |
82 | 79 | context: OpExecutionContext, |
83 | | - exception: Exception, |
84 | 80 | config: MatrixProfileJobConfig, |
85 | 81 | s3_object_storage: S3ObjectStorageResource, |
| 82 | + logs: Union[str, None], |
| 83 | + error_message: Union[str, None], |
86 | 84 | ) -> MatrixProfileJobResult: |
87 | | - context.log.error("Failed to run Matrix Profile job: %s", exception) |
| 85 | + """Handle a failed Matrix Profile job by processing logs and creating result. |
| 86 | +
|
| 87 | + Args: |
| 88 | + context: Dagster op execution context. |
| 89 | + config: Job configuration. |
| 90 | + s3_object_storage: S3 storage resource for uploading logs. |
| 91 | + logs: Pod logs retrieved before job cleanup (may be None). |
| 92 | + error_message: Error message from job failure. |
| 93 | +
|
| 94 | + Returns: |
| 95 | + MatrixProfileJobResult with error information. |
| 96 | + """ |
| 97 | + context.log.error("Matrix Profile job failed: %s", error_message) |
88 | 98 |
|
89 | | - # Attempt to retrieve and process pod logs for user-friendly error reporting |
90 | 99 | error_summary = None |
91 | 100 | error_logs_key = None |
92 | 101 |
|
93 | | - job_name = parse_job_name_from_exception(exception) |
94 | | - |
95 | | - if job_name: |
96 | | - namespace = get_current_namespace() |
97 | | - context.log.info( |
98 | | - "Attempting to retrieve logs for job %s in namespace %s", |
99 | | - job_name, |
100 | | - namespace, |
| 102 | + if logs: |
| 103 | + # Extract user-friendly error summary |
| 104 | + error_summary = extract_error_summary(logs) |
| 105 | + context.log.debug("Extracted error summary: %s", error_summary) |
| 106 | + |
| 107 | + # Upload full logs to S3 for reference |
| 108 | + s3_client = s3_object_storage.get_client() |
| 109 | + error_logs_key, upload_error = upload_logs_to_s3( |
| 110 | + s3_client=s3_client, |
| 111 | + bucket=s3_object_storage.job_outputs_bucket_name, |
| 112 | + logs=logs, |
| 113 | + workflow_job_id=config.workflow_job_id, |
101 | 114 | ) |
102 | 115 |
|
103 | | - logs, logs_error = retrieve_pod_logs(namespace=namespace, job_name=job_name) |
104 | | - |
105 | | - if logs: |
106 | | - # Extract user-friendly error summary |
107 | | - error_summary = extract_error_summary(logs) |
108 | | - context.log.debug("Extracted error summary: %s", error_summary) |
109 | | - |
110 | | - # Upload full logs to S3 for reference |
111 | | - s3_client = s3_object_storage.get_client() |
112 | | - error_logs_key, upload_error = upload_logs_to_s3( |
113 | | - s3_client=s3_client, |
114 | | - bucket=s3_object_storage.job_outputs_bucket_name, |
115 | | - logs=logs, |
116 | | - workflow_job_id=config.workflow_job_id, |
117 | | - ) |
118 | | - |
119 | | - if upload_error: |
120 | | - context.log.warning("Failed to upload logs to S3: %s", upload_error) |
121 | | - else: |
122 | | - context.log.warning("Failed to retrieve pod logs: %s", logs_error) |
| 116 | + if upload_error: |
| 117 | + context.log.warning("Failed to upload logs to S3: %s", upload_error) |
123 | 118 | else: |
124 | | - context.log.warning( |
125 | | - "Could not parse job name from exception, unable to retrieve pod logs" |
126 | | - ) |
| 119 | + context.log.warning("No pod logs available for error analysis") |
127 | 120 |
|
128 | | - # Fall back to original exception message if no summary could be extracted |
| 121 | + # Fall back to generic message if no summary could be extracted |
129 | 122 | if not error_summary: |
130 | 123 | error_summary = ( |
131 | 124 | "The analysis job failed. Please check the input data format " |
@@ -153,27 +146,32 @@ def run_matrix_profile( |
153 | 146 | "Running Matrix Profile job (image=%s) (output_key=%s)", image, output_key |
154 | 147 | ) |
155 | 148 |
|
156 | | - env_vars = { |
157 | | - "S3_ACCESS_KEY_ID": s3_object_storage.access_key_id, |
158 | | - "S3_SECRET_ACCESS_KEY": s3_object_storage.secret_access_key, |
159 | | - "OUTPUT_KEY": output_key, |
160 | | - "OUTPUT_BUCKET": config.output_bucket, |
161 | | - "FILE_URL": config.file_url, |
162 | | - "ANALYSIS_VARIABLE": config.analysis_variable, |
163 | | - } |
164 | | - |
165 | | - env_vars = ["{}={}".format(k, v) for k, v in env_vars.items()] |
| 149 | + env_vars = [ |
| 150 | + "S3_ACCESS_KEY_ID={}".format(s3_object_storage.access_key_id), |
| 151 | + "S3_SECRET_ACCESS_KEY={}".format(s3_object_storage.secret_access_key), |
| 152 | + "OUTPUT_KEY={}".format(output_key), |
| 153 | + "OUTPUT_BUCKET={}".format(config.output_bucket), |
| 154 | + "FILE_URL={}".format(config.file_url), |
| 155 | + "ANALYSIS_VARIABLE={}".format(config.analysis_variable), |
| 156 | + ] |
| 157 | + |
| 158 | + # Use custom job execution that captures logs before cleanup on failure |
| 159 | + job_result = execute_k8s_job_with_log_capture( |
| 160 | + context=context, |
| 161 | + image=image, |
| 162 | + env_vars=env_vars, |
| 163 | + image_pull_policy=config.image_pull_policy, |
| 164 | + timeout=config.timeout_secs, |
| 165 | + ) |
166 | 166 |
|
167 | | - try: |
168 | | - execute_k8s_job( |
| 167 | + if not job_result.success: |
| 168 | + return handle_job_failure( |
169 | 169 | context=context, |
170 | | - image=image, |
171 | | - image_pull_policy=config.image_pull_policy, |
172 | | - timeout=config.timeout_secs, |
173 | | - env_vars=env_vars, |
| 170 | + config=config, |
| 171 | + s3_object_storage=s3_object_storage, |
| 172 | + logs=job_result.logs, |
| 173 | + error_message=job_result.error_message, |
174 | 174 | ) |
175 | | - except Exception as ex: |
176 | | - return handle_job_failure(context, ex, config, s3_object_storage) |
177 | 175 |
|
178 | 176 | context.log.info( |
179 | 177 | "Checking if output report exists: s3://%s/%s", |
|
0 commit comments