Skip to content

Commit 3cbdc6b

Browse files
authored
V2.2.7 chg midterm spring2021 (#79)
* FIX repeat background image * switch pipeline healthcheck to http request * CHG python 3.8 -> 3.9 * fixes for misterm 2021 * ADD question editor * ADD warning to question reset * CHG minor word edits * ADD question add and delete to editor * ADD logging to admin questions endpoint * LINT
1 parent 01295a4 commit 3cbdc6b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1381
-429
lines changed

Makefile

+9-29
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
PERSISTENT_SERVICES := db traefik kibana elasticsearch-coordinating redis-master logstash adminer
2-
RESTART_ALWAYS_SERVICES := api web rpc-worker
1+
PERSISTENT_SERVICES := db traefik kibana elasticsearch-coordinating redis-master logstash adminer
2+
RESTART_ALWAYS_SERVICES := api web-dev rpc-worker
33
PUSH_SERVICES := api web logstash theia-init theia-proxy theia-admin theia-xv6
4-
BUILD_ALLWAYS := api web
54

65

76

@@ -32,17 +31,22 @@ help:
3231
@echo 'Available make targets:'
3332
@grep PHONY: Makefile | cut -d: -f2 | sed '1d;s/^/make/'
3433

34+
.PHONY: deploy # Deploy Anubis to cluster
35+
deploy:
36+
./kube/deploy.sh
37+
./kube/restart.sh
38+
3539
.PHONY: build # Build all docker images
3640
build:
37-
docker-compose build --parallel --pull $(BUILD_ALLWAYS)
41+
docker-compose build --parallel --pull
3842

3943
.PHONY: push # Push images to registry.osiris.services (requires vpn)
4044
push:
4145
docker-compose build --parallel --pull $(PUSH_SERVICES)
4246
docker-compose push $(PUSH_SERVICES)
4347

4448
.PHONY: debug # Start the cluster in debug mode
45-
debug: build
49+
debug:
4650
docker-compose up -d $(PERSISTENT_SERVICES)
4751
docker-compose up \
4852
-d --force-recreate \
@@ -69,36 +73,12 @@ mindebug: build
6973
@echo 'auth: http://localhost/api/admin/auth/token/jmc1283'
7074
@echo 'site: http://localhost/'
7175

72-
73-
.PHONY: jupyter # Start he jupyterhub container
74-
jupyter:
75-
docker-compose up --force-recreate --build api-dev
76-
77-
.PHONY: deploy # Start the cluster in production mode
78-
deploy: check build restart
79-
80-
.PHONY: backup # Backup database to file
81-
backup:
82-
./scripts/backup.sh
83-
84-
.PHONY: restore # Restore to most recent backup
85-
restore:
86-
./scripts/restore.sh
87-
8876
yeetdb:
8977
docker-compose kill db
9078
docker-compose rm -f
9179
docker volume rm anubis_db_data
9280
docker-compose up -d --force-recreate db
9381

94-
.PHONY: cleandata # yeet data
95-
cleandata:
96-
docker-compose kill
97-
docker-compose rm -f
98-
if [ -n "${VOLUMES}" ]; then \
99-
docker volume rm $(VOLUMES); \
100-
fi
101-
10282
.PHONY: clean # Clean up volumes, images and data
10383
clean:
10484
docker-compose kill

api/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.8-alpine
1+
FROM python:3.9-alpine
22

33
ENV SECRET_KEY=DEFAULT
44

api/anubis/app.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from flask import Flask
2-
from anubis.utils.logger import logger
32

43

54
def init_services(app):

api/anubis/models/__init__.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import base64
2-
import os
32
import json
3+
import os
44
from datetime import datetime, timedelta
55

66
from flask_sqlalchemy import SQLAlchemy
@@ -251,9 +251,13 @@ class AssignmentQuestion(db.Model):
251251
solution = db.Column(db.Text, nullable=True)
252252
sequence = db.Column(db.Integer, index=True, nullable=False)
253253
code_question = db.Column(db.Boolean, default=False)
254-
code_language = db.Column(db.String(128), nullable=True, default=None)
254+
code_language = db.Column(db.String(128), nullable=True, default='')
255255
placeholder = db.Column(db.Text, nullable=True, default="")
256256

257+
# Timestamps
258+
created = db.Column(db.DateTime, default=datetime.now)
259+
last_updated = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
260+
257261
# Relationships
258262
assignment = db.relationship(Assignment, cascade="all,delete", backref="questions")
259263

@@ -630,7 +634,7 @@ class StaticFile(db.Model):
630634
filename = db.Column(db.String(256))
631635
path = db.Column(db.String(256))
632636
content_type = db.Column(db.String(128))
633-
blob = db.Column(db.LargeBinary(length=(2**32)-1))
637+
blob = db.Column(db.LargeBinary(length=(2 ** 32) - 1))
634638
hidden = db.Column(db.Boolean)
635639

636640
# Timestamps

api/anubis/rpc/pipeline.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
from kubernetes import config, client
77

88
from anubis.models import Config, Submission
9-
from anubis.utils.logger import logger
109
from anubis.utils.data import is_debug
10+
from anubis.utils.logger import logger
11+
1112

1213
def create_pipeline_job_obj(client, submission):
1314
"""
@@ -31,6 +32,7 @@ def create_pipeline_job_obj(client, submission):
3132
image=submission.assignment.pipeline_image,
3233
image_pull_policy=os.environ.get("IMAGE_PULL_POLICY", default="Always"),
3334
env=[
35+
client.V1EnvVar(name="NETID", value=submission.owner.netid),
3436
client.V1EnvVar(name="TOKEN", value=submission.token),
3537
client.V1EnvVar(name="COMMIT", value=submission.commit),
3638
client.V1EnvVar(name="GIT_REPO", value=submission.repo.repo_url),

api/anubis/rpc/theia.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import os
22
import time
3-
from datetime import datetime, timedelta
3+
from datetime import datetime
44

55
from kubernetes import config, client
66

77
from anubis.models import db, Config, TheiaSession
8-
from anubis.utils.logger import logger
98
from anubis.utils.elastic import esindex
9+
from anubis.utils.logger import logger
1010

1111

1212
def get_theia_pod_name(theia_session: TheiaSession) -> str:

api/anubis/utils/assignments.py

+30-30
Original file line numberDiff line numberDiff line change
@@ -60,44 +60,44 @@ def get_assignments(netid: str, course_id=None) -> Union[List[Dict[str, str]], N
6060

6161
assignments = (
6262
Assignment.query.join(Course)
63-
.join(InCourse)
64-
.join(User)
65-
.filter(
63+
.join(InCourse)
64+
.join(User)
65+
.filter(
6666
User.netid == netid,
6767
Assignment.hidden == False,
6868
Assignment.release_date <= datetime.now(),
6969
*filters
7070
)
71-
.order_by(Assignment.due_date.desc())
72-
.all()
71+
.order_by(Assignment.due_date.desc())
72+
.all()
7373
)
7474

7575
a = [a.data for a in assignments]
7676
for assignment_data in a:
7777
assignment_data["has_submission"] = (
78-
Submission.query.join(User)
79-
.join(Assignment)
80-
.filter(
81-
Assignment.id == assignment_data["id"],
82-
User.netid == netid,
83-
)
84-
.first()
85-
is not None
78+
Submission.query.join(User)
79+
.join(Assignment)
80+
.filter(
81+
Assignment.id == assignment_data["id"],
82+
User.netid == netid,
83+
)
84+
.first()
85+
is not None
8686
)
8787
assignment_data["has_repo"] = (
88-
AssignmentRepo.query.filter(
89-
AssignmentRepo.owner_id == user.id,
90-
AssignmentRepo.assignment_id == assignment_data['id'],
91-
).first()
92-
is not None
88+
AssignmentRepo.query.filter(
89+
AssignmentRepo.owner_id == user.id,
90+
AssignmentRepo.assignment_id == assignment_data['id'],
91+
).first()
92+
is not None
9393
)
9494

9595
return a
9696

9797

9898
@cache.memoize(timeout=3, unless=is_debug)
9999
def get_submissions(
100-
user_id=None, course_id=None, assignment_id=None
100+
user_id=None, course_id=None, assignment_id=None
101101
) -> Union[List[Dict[str, str]], None]:
102102
"""
103103
Get all submissions for a given netid. Cache the results. Optionally specify
@@ -127,11 +127,11 @@ def get_submissions(
127127

128128
submissions = (
129129
Submission.query.join(Assignment)
130-
.join(Course)
131-
.join(InCourse)
132-
.join(User)
133-
.filter(Submission.owner_id == owner.id, *filters)
134-
.all()
130+
.join(Course)
131+
.join(InCourse)
132+
.join(User)
133+
.filter(Submission.owner_id == owner.id, *filters)
134+
.all()
135135
)
136136

137137
return [s.full_data for s in submissions]
@@ -175,10 +175,10 @@ def assignment_sync(assignment_data):
175175
db.session.commit()
176176

177177
for i in AssignmentTest.query.filter(
178-
and_(
179-
AssignmentTest.assignment_id == assignment.id,
180-
AssignmentTest.name.notin_(assignment_data["tests"]),
181-
)
178+
and_(
179+
AssignmentTest.assignment_id == assignment.id,
180+
AssignmentTest.name.notin_(assignment_data["tests"]),
181+
)
182182
).all():
183183
db.session.delete(i)
184184
db.session.commit()
@@ -189,8 +189,8 @@ def assignment_sync(assignment_data):
189189
Assignment.id == assignment.id,
190190
AssignmentTest.name == test_name,
191191
)
192-
.join(Assignment)
193-
.first()
192+
.join(Assignment)
193+
.first()
194194
)
195195

196196
if assignment_test is None:

api/anubis/utils/data.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
from datetime import datetime
12
from email.mime.text import MIMEText
23
from hashlib import sha256
34
from json import dumps
45
from os import environ, urandom
56
from smtplib import SMTP
67
from typing import Union, Tuple
7-
from datetime import datetime
88

99
from flask import Response
1010

@@ -214,7 +214,7 @@ def split_chunks(lst, n):
214214
"""
215215
_chunks = []
216216
for i in range(0, len(lst), n):
217-
_chunks.append(lst[i : i + n])
217+
_chunks.append(lst[i: i + n])
218218
return _chunks
219219

220220

api/anubis/utils/decorators.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ def json_wrap(*args, **kwargs):
5050

5151

5252
def json_endpoint(
53-
required_fields: Union[List[str], List[Tuple], None] = None,
54-
only_required: bool = False,
53+
required_fields: Union[List[str], List[Tuple], None] = None,
54+
only_required: bool = False,
5555
):
5656
"""
5757
Wrap a route so that it always converts data
@@ -69,13 +69,13 @@ def wrapper(func):
6969
@wraps(func)
7070
def json_wrap(*args, **kwargs):
7171
if not request.headers.get("Content-Type", default=None).startswith(
72-
"application/json"
72+
"application/json"
7373
):
7474
return {
75-
"success": False,
76-
"error": "Content-Type header is not application/json",
77-
"data": None,
78-
}, 406 # Not Acceptable
75+
"success": False,
76+
"error": "Content-Type header is not application/json",
77+
"data": None,
78+
}, 406 # Not Acceptable
7979
json_data: dict = request.json
8080

8181
if required_fields is not None:

api/anubis/utils/elastic.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ def wrapper(*args, **kwargs):
3434
ip = get_request_ip()
3535
location = geolite2.lookup(ip)
3636
longitude, latitude = location.location[::-1] \
37-
if location is not None \
38-
else [0, 0]
37+
if location is not None \
38+
else [0, 0]
3939

4040
# Skip indexing if the app has ELK disabled
4141
if not config.DISABLE_ELK:

api/anubis/utils/questions.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515

1616
def get_question_sequence_mapping(
17-
questions: List[AssignmentQuestion],
17+
questions: List[AssignmentQuestion],
1818
) -> Dict[int, List[AssignmentQuestion]]:
1919
"""
2020
Get mapping of sequence to question mapping from list of questions
@@ -91,8 +91,8 @@ def assign_questions(assignment: Assignment):
9191
assigned_questions = []
9292
students = (
9393
User.query.join(InCourse)
94-
.filter(InCourse.course_id == assignment.course_id)
95-
.all()
94+
.filter(InCourse.course_id == assignment.course_id)
95+
.all()
9696
)
9797
for student in students:
9898
for sequence, qs in questions.items():

api/anubis/utils/rpc.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from redis import Redis
22
from rq import Queue
33

4+
from anubis.config import config
45
from anubis.rpc.pipeline import test_repo
6+
from anubis.rpc.seed import seed_debug
57
from anubis.rpc.theia import (
68
initialize_theia_session,
79
reap_theia_session,
810
reap_stale_theia_sessions,
911
)
10-
from anubis.rpc.seed import seed_debug
11-
from anubis.config import config
1212

1313

1414
def rpc_enqueue(func, *args):

api/anubis/utils/stats.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ def stats_for(student_id, assignment_id):
1212
best = None
1313
best_count = -1
1414
for submission in (
15-
Submission.query.filter(
16-
Submission.assignment_id == assignment_id,
17-
Submission.owner_id == student_id,
18-
Submission.processed,
19-
)
20-
.order_by(Submission.created.desc())
21-
.all()
15+
Submission.query.filter(
16+
Submission.assignment_id == assignment_id,
17+
Submission.owner_id == student_id,
18+
Submission.processed,
19+
)
20+
.order_by(Submission.created.desc())
21+
.all()
2222
):
2323
correct_count = sum(
2424
map(lambda result: 1 if result.passed else 0, submission.test_results)
@@ -80,8 +80,8 @@ def bulk_stats(assignment_id, netids=None, offset=0, limit=20):
8080
bests = []
8181

8282
assignment = (
83-
Assignment.query.filter_by(name=assignment_id).first()
84-
or Assignment.query.filter_by(id=assignment_id).first()
83+
Assignment.query.filter_by(name=assignment_id).first()
84+
or Assignment.query.filter_by(id=assignment_id).first()
8585
)
8686
if assignment is None:
8787
return error_response("assignment does not exist")

0 commit comments

Comments
 (0)