Skip to content

Commit 9af60ea

Browse files
committed
Server: runner and job api
1 parent 5498f07 commit 9af60ea

File tree

9 files changed

+156
-63
lines changed

9 files changed

+156
-63
lines changed

file_storage/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*

hwut_server/api/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from hwut_server.api.about import mod as mod_about
2-
from hwut_server.api.execute import mod as mod_execute
2+
from hwut_server.api.jobs import mod as mod_jobs
33
from hwut_server.api.runners import mod as mod_runners
44
from hwut_server.api.targets import mod as mod_targets
55

@@ -10,6 +10,6 @@ def register(app):
1010
"""
1111

1212
app.register_blueprint(mod_about)
13-
app.register_blueprint(mod_execute)
13+
app.register_blueprint(mod_jobs)
1414
app.register_blueprint(mod_runners)
1515
app.register_blueprint(mod_targets)

hwut_server/api/execute.py

-31
This file was deleted.

hwut_server/api/jobs.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import os
2+
from secrets import token_urlsafe
3+
from flask import Blueprint, jsonify, request, redirect, abort
4+
from datetime import datetime
5+
6+
from hwut_server.decorators import requires_authentication, check_authentication
7+
from hwut_server.models.jobs import Jobs, JobStatus
8+
from hwut_server.models.targets import Microcontrollers, Boards
9+
from hwut_server.database import db
10+
from hwut_server.utils import FILE_STORAGE
11+
12+
mod = Blueprint('jobs', __name__, url_prefix='/jobs')
13+
14+
15+
@mod.route('/submit', methods=['PUT'])
16+
@requires_authentication
17+
def submit():
18+
user = request.authorization.username
19+
20+
duration_limit_seconds = 60
21+
if request.args.get('duration_limit_seconds'):
22+
duration_limit_seconds = request.args.get('duration_limit_seconds')
23+
if duration_limit_seconds > 300:
24+
return abort(413, 'maximum execution time is 300 seconds')
25+
26+
# board argument is optional
27+
board = request.args.get('board')
28+
if board:
29+
try:
30+
Boards.query.filter(Boards.name == board).one()
31+
except:
32+
return abort(400, 'board in unknown')
33+
34+
microcontroller = request.args.get('microcontroller')
35+
if microcontroller is None:
36+
return abort(400, 'microcontroller must be specified')
37+
else:
38+
try:
39+
Microcontrollers.query.filter(Microcontrollers.name == microcontroller).one()
40+
except:
41+
return abort(400, 'microcontroller in unknown')
42+
43+
filename_executable = token_urlsafe(32)
44+
size = request.content_length
45+
if size is None or size < 500: # minimum size 0.5kB
46+
return abort(400, 'executable file is missing or empty')
47+
if size is not None and size > 5000000: # Size limit 5MB
48+
return abort(413, 'maximum executable file size is 5MB')
49+
with open(os.path.join(FILE_STORAGE, filename_executable), 'wb') as f:
50+
f.write(request.stream.read())
51+
52+
try:
53+
job = Jobs(
54+
datetime.now(),
55+
JobStatus.WAITING,
56+
duration_limit_seconds,
57+
filename_executable,
58+
user,
59+
board,
60+
microcontroller,
61+
)
62+
if request.args.get('comment'):
63+
job.comment = request.args.get('comment')
64+
db.session.add(job)
65+
db.session.commit()
66+
except:
67+
return abort(500, 'unable to create runner')
68+
return redirect(mod.url_prefix + '/' + str(job.id), 201)
69+
70+
71+
@mod.route('/<int:id>', methods=['GET'])
72+
def get(id):
73+
job = Jobs.query.filter(Jobs.id == id).one()
74+
auth = request.authorization
75+
extended = (auth and ((check_authentication(auth.username, auth.password, superuser=True)) or (
76+
check_authentication(auth.username, auth.password) and auth.username == job.owner)))
77+
return jsonify(job.to_dict(extended))
78+
79+
80+
@mod.route('/<int:id>', methods=['DELETE'])
81+
@requires_authentication
82+
def cancel(id):
83+
job = Jobs.query.filter(Jobs.id == id).one()
84+
auth = request.authorization
85+
if auth and (
86+
(check_authentication(auth.username, auth.password, superuser=True))
87+
or (check_authentication(auth.username, auth.password) and auth.username == job.owner)):
88+
if job.status == JobStatus.WAITING:
89+
job.status = JobStatus.CANCELED
90+
db.session.commit()
91+
return jsonify(job.to_dict(True)), 204
92+
else:
93+
return abort(400, 'Unable to cancel job. Current job status: {}'.format(job.status.name))
94+
else:
95+
return abort(401)
96+
97+
98+
@mod.route('', methods=['GET'])
99+
@requires_authentication
100+
def list_jobs():
101+
auth = request.authorization
102+
if check_authentication(auth.username, auth.password, superuser=True):
103+
job_list = Jobs.query.all()
104+
else:
105+
job_list = Jobs.query.filter(Jobs.owner == auth.username).all()
106+
extended = False
107+
if request.args.get('extended') and request.args.get('extended') == '1':
108+
extended = True
109+
return jsonify([job.to_dict(extended) for job in job_list])

hwut_server/api/runners.py

+5-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from hwut_server.models import Boards, Microcontrollers
55
from hwut_server.models.runners import Runners
6-
from hwut_server.decorators import requires_authentication, requires_superuser
6+
from hwut_server.decorators import requires_authentication, requires_superuser, check_authentication
77
from hwut_server.utils import dict_list_extended_if_authentication
88
from hwut_server.database import db
99

@@ -33,7 +33,10 @@ def runners_list(filter_type, filter1, filter2):
3333
def targets_boards_get(id):
3434
try:
3535
runner = Runners.query.filter(Runners.id == id).one()
36-
if True: # FIXME: if user is owner os superuser
36+
auth = request.authorization
37+
if auth and (
38+
(check_authentication(auth.username, auth.password, superuser=True))
39+
or (check_authentication(auth.username, auth.password) and auth.username == runner.owner)):
3740
return jsonify(runner.to_dict_long())
3841
else:
3942
return jsonify(runner.to_dict_short())
@@ -47,23 +50,15 @@ def runner_create():
4750
if (request.args.get('board') is None) or (request.args.get('microcontroller') is None):
4851
abort(400)
4952

50-
# FIXME: get user
5153
user = request.authorization.username
52-
5354
try:
5455
board = Boards.query.filter(Boards.name == request.args.get('board')).one()
55-
except MultipleResultsFound:
56-
abort(500, 'board "{}" exists multiple time in database'.format(request.args.get('board')))
57-
return
5856
except NoResultFound:
5957
abort(410, 'board "{}" does not exist'.format(request.args.get('board')))
6058
return
6159
try:
6260
microcontroller = Microcontrollers.query\
6361
.filter(Microcontrollers.name == request.args.get('microcontroller')).one()
64-
except MultipleResultsFound:
65-
abort(500, 'microcontroller exists multiple time in database')
66-
return
6762
except NoResultFound:
6863
abort(410, 'microcontroller does not exist')
6964
return

hwut_server/models/jobs.py

+35-19
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,55 @@ class JobStatus(Enum):
77
WAITING = 10
88
RUNNING = 20
99
FINISHED = 30
10+
CANCELED = 40
1011

1112

1213
class Jobs(db.Model):
1314
__tablename__ = 'jobs'
1415
id = db.Column('id', db.BigInteger, db.Sequence('jobs_id_seq'), primary_key=True, index=True, unique=True,
1516
autoincrement=True)
16-
created = db.Column(db.TIMESTAMP, nullable=False)
17-
uploaded = db.Column(db.TIMESTAMP, nullable=False)
18-
status = db.Column(db.Enum(JobStatus))
19-
name = db.Column(db.Text)
17+
created = db.Column(db.DateTime, nullable=False)
18+
status = db.Column(db.Enum(JobStatus), nullable=False)
19+
comment = db.Column(db.Text)
20+
duration_limit_seconds = db.Column(db.Integer, nullable=False)
2021
filename_executable = db.Column(db.Text, nullable=False)
2122
filename_log = db.Column(db.Text)
2223
filename_other = db.Column(db.Text)
2324
owner = db.Column(db.Text, db.ForeignKey('users.name'), nullable=False)
25+
board = db.Column(db.Text, db.ForeignKey('boards.name'))
26+
microcontroller = db.Column(db.Text, db.ForeignKey('microcontrollers.name'), nullable=False)
2427

25-
def __init__(self, created, uploaded, name):
28+
def __init__(self, created, status, duration_limit_seconds, filename_executable, owner, board, microcontroller):
2629
# 'id' auto increment
2730
self.created = created
28-
self.uploaded = uploaded
29-
self.name = name
31+
self.status = status
32+
self.duration_limit_seconds = duration_limit_seconds
33+
self.filename_executable = filename_executable
34+
self.owner = owner
35+
self.board = board
36+
self.microcontroller = microcontroller
3037

3138
def __repr__(self):
3239
return '<job %i>' % self.id
3340

34-
def to_dict_short(self):
35-
return {
36-
'id': self.id,
37-
'name': self.name,
38-
}
39-
40-
def to_dict_long(self):
41-
return {
42-
'id': self.id,
43-
'name': self.name,
44-
'created': self.created,
45-
}
41+
def to_dict(self, extended=True):
42+
if extended:
43+
return {
44+
'id': self.id,
45+
'created': self.created,
46+
'status': self.status.name,
47+
'comment': self.comment,
48+
'duration_limit_seconds': self.duration_limit_seconds,
49+
'filename_log': self.filename_log,
50+
'filename_other': self.filename_other,
51+
'owner': self.owner,
52+
'board': self.board,
53+
'microcontroller': self.microcontroller,
54+
}
55+
else:
56+
return {
57+
'id': self.id,
58+
'created': self.created,
59+
'status': self.status.name,
60+
'owner': self.owner,
61+
}

hwut_server/models/targets.py

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class Boards(db.Model):
88
name = db.Column(db.Text, primary_key=True, unique=True, nullable=False)
99
manufacturer = db.Column(db.Text)
1010
runners = relationship("Runners")
11+
jobs = relationship("Jobs")
1112

1213
def __init__(self, name):
1314
self.name = name
@@ -33,6 +34,7 @@ class Microcontrollers(db.Model):
3334
name = db.Column(db.Text, primary_key=True, unique=True, nullable=False)
3435
manufacturer = db.Column(db.Text)
3536
runners = relationship("Runners")
37+
jobs = relationship("Jobs")
3638

3739
def __init__(self, name):
3840
self.name = name

hwut_server/runner_api/jobs.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from hwut_server.decorators import requires_authentication
44

5-
mod = Blueprint('jobs', __name__)
5+
mod = Blueprint('runner_jobs', __name__)
66

77

88
@mod.route('/get', methods=['GET'])

hwut_server/utils.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from hwut_server.decorators import check_authentication, authenticate
22

3+
FILE_STORAGE = './file_storage'
34

45
def dict_list_extended_if_authentication(r, l):
56
if r.args.get('extended') == '1':

0 commit comments

Comments
 (0)