-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.py
161 lines (146 loc) · 4.44 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import os
from zipfile import ZipFile
import logging
import shutil
import requests
import queue
import secrets
from datetime import datetime
from pathlib import Path
from flask import Flask, request, jsonify
from dispatcher.dispatcher import Dispatcher
logging.basicConfig(filename='logs/sandbox.log')
app = Flask(__name__)
if __name__ != '__main__':
# let flask app use gunicorn's logger
gunicorn_logger = logging.getLogger('gunicorn.error')
app.logger.handlers = gunicorn_logger.handlers
app.logger.setLevel(gunicorn_logger.level)
logging.getLogger().setLevel(gunicorn_logger.level)
logger = app.logger
# data storage
SUBMISSION_DIR = Path(os.getenv(
'SUBMISSION_DIR',
'submissions',
))
SUBMISSION_BACKUP_DIR = Path(
os.getenv(
'SUBMISSION_BACKUP_DIR',
'submissions.bk',
))
SUBMISSION_HOST_DIR = os.getenv(
'SUBMISSION_HOST_DIR',
'/submissions',
)
# check
if SUBMISSION_DIR == SUBMISSION_BACKUP_DIR:
logger.error('use the same dir for submission and backup!')
# create directory
SUBMISSION_DIR.mkdir(exist_ok=True)
SUBMISSION_BACKUP_DIR.mkdir(exist_ok=True)
# backend config
BACKEND_API = os.environ.get(
'BACKEND_API',
f'http://web:8080',
)
# sandbox token
SANDBOX_TOKEN = os.getenv(
'SANDBOX_TOKEN',
'KoNoSandboxDa',
)
def clean_data(submission_id):
submission_dir = SUBMISSION_DIR / submission_id
shutil.rmtree(submission_dir)
def backup_data(submission_id):
submission_dir = SUBMISSION_DIR / submission_id
dest = SUBMISSION_BACKUP_DIR / f'{submission_id}_{datetime.now().strftime("%Y-%m-%d_%H:%M:%S")}'
shutil.move(submission_dir, dest)
def recieve_result(
submission_id: str,
data: dict,
):
data['token'] = SANDBOX_TOKEN
# extract files
files = [(
'files',
(f.name.split('/')[-1], f, None),
) for f in data['files']]
del data['files']
logger.info(f'send {submission_id} to BE server')
resp = requests.put(
f'{BACKEND_API}/submission/{submission_id}/complete',
data=data,
files=files,
)
logger.debug(f'get BE response: [{resp.status_code}] {resp.text}', )
# clear
if resp.status_code == 200 and app.logger.level != logging.DEBUG:
clean_data(submission_id)
# copy to another place
else:
backup_data(submission_id)
return True
# setup dispatcher
DISPATCHER_CONFIG = os.environ.get(
'DISPATCHER_CONFIG',
'.config/dispatcher.json',
)
DISPATCHER = Dispatcher(
dispatcher_config=DISPATCHER_CONFIG,
on_complete=recieve_result,
)
DISPATCHER.start()
@app.route('/<submission_id>', methods=['POST'])
def submit(submission_id):
token = request.values['token']
if not secrets.compare_digest(token, SANDBOX_TOKEN):
logger.debug(f'get invalid token: {token}')
return 'invalid token', 403
# make submission directory
submission_dir = SUBMISSION_DIR / submission_id
submission_dir.mkdir()
# save attachments
atts = request.files.getlist('attachments')
for a in atts:
a.save(submission_dir / a.filename)
# save input and output
testcase = request.files.get('testcase')
if testcase is not None:
with ZipFile(testcase, 'r') as z:
z.extractall(submission_dir)
# save source code
code = request.values['src']
if type(code) != type(''):
return 'code should be string', 400
(submission_dir / 'main.py').write_text(code)
logger.debug(f'send submission {submission_id} to dispatcher')
try:
DISPATCHER.handle(submission_id)
except queue.Full:
return jsonify({
'status': 'err',
'msg': 'task queue is full now.\n'
'please wait a moment and re-send the submission.',
'data': None,
}), 500
return jsonify({
'status': 'ok',
'msg': 'ok',
'data': 'ok',
})
@app.route('/status', methods=['GET'])
def status():
ret = {
'load': DISPATCHER.queue.qsize() / DISPATCHER.max_task_count,
}
# if token is provided
if secrets.compare_digest(SANDBOX_TOKEN, request.args.get('token', '')):
ret.update({
'queueSize': DISPATCHER.queue.qsize(),
'maxTaskCount': DISPATCHER.max_task_count,
'containerCount': DISPATCHER.container_count,
'maxContainerCount': DISPATCHER.max_container_count,
'submissions': [*DISPATCHER.submission_ids],
'running': DISPATCHER.do_run,
})
return jsonify(ret), 200