Skip to content

Commit 9be59d8

Browse files
author
Nils Bars
committed
Add friendly error handling for server connection failures in task tool
Wrap requests.post() calls in server_post() helper that catches RequestException, logs the full traceback to a root-only rotating log file (/var/log/ref-task.log), and shows a user-friendly message instead of a raw Python traceback.
1 parent 6d5e74b commit 9be59d8

1 file changed

Lines changed: 59 additions & 14 deletions

File tree

ref-docker-base/task.py

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import argparse
44
import importlib.machinery
55
import importlib.util
6+
import logging
7+
from logging.handlers import RotatingFileHandler
68
import os
79
import sys
10+
import traceback
811
import typing as ty
912
import shutil
1013
from pathlib import Path
@@ -32,6 +35,28 @@
3235
IS_SUBMISSION = os.path.isfile("/etc/is_submission")
3336
MAX_TEST_OUTPUT_LENGTH = 1024 * 64
3437

38+
_LOG_PATH = "/var/log/ref-task.log"
39+
40+
41+
class _SecureRotatingFileHandler(RotatingFileHandler):
42+
"""RotatingFileHandler that enforces 0600 permissions on every file it creates."""
43+
44+
def _open(self):
45+
old = os.umask(0o077)
46+
try:
47+
return super()._open()
48+
finally:
49+
os.umask(old)
50+
51+
52+
_error_logger = logging.getLogger("ref.task")
53+
_error_logger.setLevel(logging.ERROR)
54+
_log_handler = _SecureRotatingFileHandler(
55+
_LOG_PATH, maxBytes=1024 * 1024, backupCount=1
56+
)
57+
_log_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
58+
_error_logger.addHandler(_log_handler)
59+
3560

3661
def finalize_request(req):
3762
signer = TimedSerializer(KEY, salt="from-container-to-web")
@@ -40,6 +65,19 @@ def finalize_request(req):
4065
return req
4166

4267

68+
def server_post(url: str, **kwargs) -> requests.Response:
69+
"""POST to the submission server, handling connection errors gracefully."""
70+
try:
71+
return requests.post(url, **kwargs)
72+
except requests.exceptions.RequestException:
73+
_error_logger.error("Request to %s failed:\n%s", url, traceback.format_exc())
74+
print_err("[!] Failed to connect to the submission server.")
75+
print_err(
76+
"[!] Please try again later. If this problem persists, contact a supervisor."
77+
)
78+
exit(1)
79+
80+
4381
def handle_response(resp, expected_status=(200,)) -> ty.Tuple[int, ty.Dict]:
4482
"""
4583
Process a response of a "requests" request.
@@ -101,7 +139,7 @@ def cmd_reset(_):
101139
)
102140
req = {}
103141
req = finalize_request(req)
104-
res = requests.post("http://ssh-reverse-proxy:8000/api/instance/reset", json=req)
142+
res = server_post("http://ssh-reverse-proxy:8000/api/instance/reset", json=req)
105143
handle_response(res)
106144

107145

@@ -178,23 +216,24 @@ def flush(self) -> None:
178216
return captured_output.getvalue(), test_results
179217

180218

181-
def cmd_submit(_):
219+
def cmd_submit(args: argparse.Namespace):
182220
print_ok("[+] Submitting instance..", flush=True)
183221

184222
test_output, test_results = _run_tests(result_will_be_submitted=True)
185223
any_test_failed = any([not t.success for t in test_results])
186224

187-
if any_test_failed:
188-
print_warn(
189-
"[!] Failing tests may indicate that your solution is erroneous or not complete yet."
190-
)
191-
print_warn("[!] Are you sure you want to submit? [y/n] ", end="")
192-
if not user_answered_yes():
193-
exit(0)
194-
else:
195-
print_ok("[+] Are you sure you want to submit? [y/n] ", end="")
196-
if not user_answered_yes():
197-
exit(0)
225+
if not args.yes:
226+
if any_test_failed:
227+
print_warn(
228+
"[!] Failing tests may indicate that your solution is erroneous or not complete yet."
229+
)
230+
print_warn("[!] Are you sure you want to submit? [y/n] ", end="")
231+
if not user_answered_yes():
232+
exit(0)
233+
else:
234+
print_ok("[+] Are you sure you want to submit? [y/n] ", end="")
235+
if not user_answered_yes():
236+
exit(0)
198237

199238
if len(test_output) > MAX_TEST_OUTPUT_LENGTH:
200239
print_err(
@@ -213,7 +252,7 @@ def cmd_submit(_):
213252
req = {"output": test_output, "test_results": [asdict(e) for e in test_results]}
214253

215254
req = finalize_request(req)
216-
res = requests.post("http://ssh-reverse-proxy:8000/api/instance/submit", json=req)
255+
res = server_post("http://ssh-reverse-proxy:8000/api/instance/submit", json=req)
217256
_, ret = handle_response(res)
218257
print_ok(ret)
219258

@@ -274,6 +313,12 @@ def main():
274313
"submit",
275314
help="Submit the current state of your work for grading. Your whole instance is submitted.",
276315
)
316+
submit_parser.add_argument(
317+
"-y",
318+
"--yes",
319+
action="store_true",
320+
help="Skip confirmation prompt and submit immediately.",
321+
)
277322
submit_parser.set_defaults(func=cmd_submit)
278323

279324
check_parser = subparsers.add_parser(

0 commit comments

Comments
 (0)