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
|
|
|
|