|
1
|
|
|
import os.path |
|
2
|
|
|
import sys |
|
3
|
|
|
import importlib |
|
4
|
|
|
|
|
5
|
|
|
from .config import read_config |
|
6
|
|
|
from .exceptions import * |
|
7
|
|
|
from .server import send_post |
|
8
|
|
|
from .filesystem import remove_working_directory |
|
9
|
|
|
|
|
10
|
|
|
import logging |
|
11
|
|
|
logger = logging.getLogger('opensubmitexec') |
|
12
|
|
|
|
|
13
|
|
|
UNSPECIFIC_ERROR = -9999 |
|
14
|
|
|
|
|
15
|
|
|
|
|
16
|
|
|
class InternalJob(): |
|
17
|
|
|
"""Internal base class for jobs, |
|
18
|
|
|
with additional private functions.""" |
|
19
|
|
|
|
|
20
|
|
|
# The current executor configuration. |
|
21
|
|
|
_config = None |
|
22
|
|
|
# Talk to the configured OpenSubmit server? |
|
23
|
|
|
_online = None |
|
24
|
|
|
# Action requested by the server (legacy) |
|
25
|
|
|
action = None |
|
26
|
|
|
|
|
27
|
|
|
submission_url = None |
|
28
|
|
|
validator_url = None |
|
29
|
|
|
result_sent = False |
|
30
|
|
|
|
|
31
|
|
|
# The base name of the validation / full test script |
|
32
|
|
|
# on disk, for importing. |
|
33
|
|
|
_validator_import_name = 'validator' |
|
34
|
|
|
|
|
35
|
|
|
def __init__(self, config=None, online=True): |
|
36
|
|
|
if config: |
|
37
|
|
|
self._config = config |
|
38
|
|
|
else: |
|
39
|
|
|
self._config = read_config() |
|
40
|
|
|
self._online = online |
|
41
|
|
|
|
|
42
|
|
|
def __str__(self): |
|
43
|
|
|
''' |
|
44
|
|
|
Nicer logging of job objects. |
|
45
|
|
|
''' |
|
46
|
|
|
return str(vars(self)) |
|
47
|
|
|
|
|
48
|
|
|
def _run_validate(self): |
|
49
|
|
|
''' |
|
50
|
|
|
Execute the validate() method in the test script belonging to this job. |
|
51
|
|
|
''' |
|
52
|
|
|
assert(os.path.exists(self.validator_script_name)) |
|
53
|
|
|
old_path = sys.path |
|
54
|
|
|
sys.path = [self.working_dir] + old_path |
|
55
|
|
|
# logger.debug('Python search path is now {0}.'.format(sys.path)) |
|
56
|
|
|
|
|
57
|
|
|
try: |
|
58
|
|
|
module = importlib.import_module(self._validator_import_name) |
|
59
|
|
|
except Exception as e: |
|
60
|
|
|
text_student = "Internal validation problem, please contact your course responsible." |
|
61
|
|
|
text_tutor = "Exception while loading the validator: " + str(e) |
|
62
|
|
|
self._send_result(text_student, text_tutor, UNSPECIFIC_ERROR) |
|
63
|
|
|
return |
|
64
|
|
|
|
|
65
|
|
|
# Looped validator loading in the test suite demands this |
|
66
|
|
|
importlib.reload(module) |
|
67
|
|
|
|
|
68
|
|
|
# make the call |
|
69
|
|
|
try: |
|
70
|
|
|
module.validate(self) |
|
71
|
|
|
except Exception as e: |
|
72
|
|
|
# get more info |
|
73
|
|
|
text_student = None |
|
74
|
|
|
text_tutor = None |
|
75
|
|
|
if type(e) is TerminationException: |
|
76
|
|
|
text_student = "The execution of '{0}' terminated unexpectely.".format( |
|
77
|
|
|
e.instance.name) |
|
78
|
|
|
text_tutor = "The execution of '{0}' terminated unexpectely.".format( |
|
79
|
|
|
e.instance.name) |
|
80
|
|
|
text_student += "\n\nOutput so far:\n" + e.output |
|
81
|
|
|
text_tutor += "\n\nOutput so far:\n" + e.output |
|
82
|
|
|
elif type(e) is TimeoutException: |
|
83
|
|
|
text_student = "The execution of '{0}' was cancelled, since it took too long.".format( |
|
84
|
|
|
e.instance.name) |
|
85
|
|
|
text_tutor = "The execution of '{0}' was cancelled due to timeout.".format( |
|
86
|
|
|
e.instance.name) |
|
87
|
|
|
text_student += "\n\nOutput so far:\n" + e.output |
|
88
|
|
|
text_tutor += "\n\nOutput so far:\n" + e.output |
|
89
|
|
|
elif type(e) is NestedException: |
|
90
|
|
|
text_student = "Unexpected problem during the execution of '{0}'. {1}".format( |
|
91
|
|
|
e.instance.name, |
|
92
|
|
|
str(e.real_exception)) |
|
93
|
|
|
text_tutor = "Unkown exception during the execution of '{0}'. {1}".format( |
|
94
|
|
|
e.instance.name, |
|
95
|
|
|
str(e.real_exception)) |
|
96
|
|
|
text_student += "\n\nOutput so far:\n" + e.output |
|
97
|
|
|
text_tutor += "\n\nOutput so far:\n" + e.output |
|
98
|
|
|
elif type(e) is WrongExitStatusException: |
|
99
|
|
|
text_student = "The execution of '{0}' resulted in the unexpected exit status {1}.".format( |
|
100
|
|
|
e.instance.name, |
|
101
|
|
|
e.got) |
|
102
|
|
|
text_tutor = "The execution of '{0}' resulted in the unexpected exit status {1}.".format( |
|
103
|
|
|
e.instance.name, |
|
104
|
|
|
e.got) |
|
105
|
|
|
text_student += "\n\nOutput so far:\n" + e.output |
|
106
|
|
|
text_tutor += "\n\nOutput so far:\n" + e.output |
|
107
|
|
|
elif type(e) is JobException: |
|
108
|
|
|
# Some problem with our own code |
|
109
|
|
|
text_student = e.info_student |
|
110
|
|
|
text_tutor = e.info_tutor |
|
111
|
|
|
elif type(e) is FileNotFoundError: |
|
112
|
|
|
text_student = "A file is missing: {0}".format( |
|
113
|
|
|
str(e)) |
|
114
|
|
|
text_tutor = "Missing file: {0}".format( |
|
115
|
|
|
str(e)) |
|
116
|
|
|
elif type(e) is AssertionError: |
|
117
|
|
|
# Need this harsh approach to kill the |
|
118
|
|
|
# test suite execution at this point |
|
119
|
|
|
# Otherwise, the problem gets lost in |
|
120
|
|
|
# the log storm |
|
121
|
|
|
logger.error( |
|
122
|
|
|
"Failed assertion in validation script. Should not happen in production.") |
|
123
|
|
|
exit(-1) |
|
124
|
|
|
else: |
|
125
|
|
|
# Something really unexpected |
|
126
|
|
|
text_student = "Internal problem while validating your submission. Please contact the course responsible." |
|
127
|
|
|
text_tutor = "Unknown exception while running the validator: {0}".format( |
|
128
|
|
|
str(e)) |
|
129
|
|
|
# We got the text. Report the problem. |
|
130
|
|
|
self._send_result(text_student, text_tutor, UNSPECIFIC_ERROR) |
|
131
|
|
|
return |
|
132
|
|
|
# no unhandled exception during the execution of the validator |
|
133
|
|
|
if not self.result_sent: |
|
134
|
|
|
logger.debug("Validation script forgot result sending, assuming success.") |
|
135
|
|
|
self.send_pass_result() |
|
136
|
|
|
# roll back |
|
137
|
|
|
sys.path = old_path |
|
138
|
|
|
# Test script was executed, result was somehow sent |
|
139
|
|
|
# Clean the file system, since we can't do anything else |
|
140
|
|
|
remove_working_directory(self.working_dir, self._config) |
|
141
|
|
|
|
|
142
|
|
|
def _send_result(self, info_student, info_tutor, error_code): |
|
143
|
|
|
post_data = [("SubmissionFileId", self.file_id), |
|
144
|
|
|
("Message", info_student), |
|
145
|
|
|
("Action", self.action), |
|
146
|
|
|
("MessageTutor", info_tutor), |
|
147
|
|
|
("ExecutorDir", self.working_dir), |
|
148
|
|
|
("ErrorCode", error_code), |
|
149
|
|
|
("Secret", self._config.get("Server", "secret")), |
|
150
|
|
|
("UUID", self._config.get("Server", "uuid")) |
|
151
|
|
|
] |
|
152
|
|
|
logger.info( |
|
153
|
|
|
'Sending result to OpenSubmit Server: ' + str(post_data)) |
|
154
|
|
|
if self._online: |
|
155
|
|
|
send_post(self._config, "/jobs/", post_data) |
|
156
|
|
|
self.result_sent = True |
|
157
|
|
|
|