Skip to content

Commit 1705d19

Browse files
authored
V3.0.3 chg digitalocean move (#87)
* CHG digital ocean move * CHG a bunch of changes for digital ocean * CHG replace old readme with design doc * CHG doc name in render.sh * CHG update design pdf * CHG update the language to reflect digital ocean on readme * CHG update design pdf * CHG fix a few things * CHG responsive question card feedback * CHG presort question responses by pool * FIX some stuff with question editing and assigning * CHG add zsh shell and optimize xv6 theia image layers * ADD github org url to course and fix question tests * CHG fix some things with the anubis cli and management IDE
1 parent 7a7a235 commit 1705d19

File tree

102 files changed

+2091
-2079
lines changed

Some content is hidden

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

102 files changed

+2091
-2079
lines changed

Makefile

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
PERSISTENT_SERVICES := db traefik kibana elasticsearch-coordinating redis-master logstash
22
RESTART_ALWAYS_SERVICES := api web-dev rpc-default rpc-theia rpc-regrade
3-
PUSH_SERVICES := api web logstash theia-init theia-proxy theia-admin theia-xv6
3+
PUSH_SERVICES := api web theia-init theia-proxy theia-admin theia-xv6
44

55

66

@@ -35,7 +35,7 @@ deploy:
3535
build:
3636
docker-compose build --parallel --pull
3737

38-
.PHONY: push # Push images to registry.osiris.services (requires vpn)
38+
.PHONY: push # Push images to registry.digitalocean.com (requires vpn)
3939
push:
4040
docker-compose build --parallel --pull $(PUSH_SERVICES)
4141
docker-compose push $(PUSH_SERVICES)
@@ -65,7 +65,7 @@ mindebug:
6565
docker-compose up -d traefik db redis-master logstash
6666
docker-compose up \
6767
-d --force-recreate \
68-
api web rpc-default rpc-theia
68+
api web-dev rpc-default rpc-theia
6969
@echo 'Waiting a moment before running migrations'
7070
sleep 3
7171
@echo 'running migrations'

README.md

+686-19
Large diffs are not rendered by default.

api/anubis/models/__init__.py

+28-27
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from sqlalchemy_json import MutableJson
88

99
from anubis.utils.data import rand
10-
from anubis.utils.services.logger import logger
1110

1211
db = SQLAlchemy()
1312

@@ -90,8 +89,11 @@ class Course(db.Model):
9089
semester = db.Column(db.TEXT, nullable=True)
9190
section = db.Column(db.TEXT, nullable=True)
9291
professor = db.Column(db.TEXT, nullable=False)
93-
theia_default_image = db.Column(db.TEXT, nullable=False, default='registry.osiris.services/anubis/xv6')
92+
autograde_tests_repo = db.Column(db.TEXT, nullable=False,
93+
default='https://github.com/os3224/anubis-assignment-tests')
94+
theia_default_image = db.Column(db.TEXT, nullable=False, default='registry.digitalocean.com/anubis/xv6')
9495
theia_default_options = db.Column(MutableJson, default=lambda: {"limits": {"cpu": "2", "memory": "500Mi"}})
96+
github_org_url = db.Column(db.TEXT, default='')
9597

9698
@property
9799
def total_assignments(self):
@@ -177,11 +179,11 @@ class Assignment(db.Model):
177179
course_id = db.Column(db.String(128), db.ForeignKey(Course.id), index=True)
178180

179181
# Fields
180-
name = db.Column(db.TEXT, nullable=False)
182+
name = db.Column(db.TEXT, nullable=False, index=True)
181183
hidden = db.Column(db.Boolean, default=False)
182184
description = db.Column(db.TEXT, nullable=True)
183185
github_classroom_url = db.Column(db.TEXT, nullable=True, default=None)
184-
pipeline_image = db.Column(db.TEXT, nullable=True)
186+
pipeline_image = db.Column(db.TEXT, nullable=True, index=True)
185187
unique_code = db.Column(
186188
db.String(8),
187189
unique=True,
@@ -191,7 +193,7 @@ class Assignment(db.Model):
191193
accept_late = db.Column(db.Boolean, default=True)
192194
autograde_enabled = db.Column(db.Boolean, default=True)
193195
theia_image = db.Column(
194-
db.TEXT, default="registry.osiris.services/anubis/theia-xv6"
196+
db.TEXT, default="registry.digitalocean.com/anubis/theia-xv6"
195197
)
196198
theia_options = db.Column(MutableJson, default=lambda: {})
197199

@@ -383,35 +385,27 @@ def data(self):
383385
384386
:return:
385387
"""
386-
response = AssignedQuestionResponse.query.filter(
388+
from anubis.utils.lms.assignments import get_assignment_due_date
389+
390+
response: AssignedQuestionResponse = AssignedQuestionResponse.query.filter(
387391
AssignedQuestionResponse.assigned_question_id == self.id,
388392
).order_by(AssignedQuestionResponse.created.desc()).first()
389393

390-
raw_response = self.question.placeholder
394+
response_data = {'submitted': None,'late': True, 'text': self.question.placeholder}
391395
if response is not None:
392-
raw_response = response.response
396+
response_data = response.data
393397

394398
return {
395399
"id": self.id,
396-
"response": raw_response,
400+
"response": response_data,
397401
"question": self.question.data,
398402
}
399403

400404
@property
401405
def full_data(self):
402-
response = AssignedQuestionResponse.query.filter(
403-
AssignedQuestionResponse.assigned_question_id == self.id,
404-
).order_by(AssignedQuestionResponse.created.desc()).first()
405-
406-
raw_response = self.question.placeholder
407-
if response is not None:
408-
raw_response = response.response
409-
410-
return {
411-
"id": self.id,
412-
"question": self.question.full_data,
413-
"response": raw_response,
414-
}
406+
data = self.data
407+
data['question'] = self.question.full_data
408+
return data
415409

416410

417411
class AssignedQuestionResponse(db.Model):
@@ -432,6 +426,16 @@ class AssignedQuestionResponse(db.Model):
432426
created = db.Column(db.DateTime, default=datetime.now)
433427
last_updated = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
434428

429+
@property
430+
def data(self):
431+
from anubis.utils.lms.assignments import get_assignment_due_date
432+
433+
return {
434+
'submitted': str(self.created),
435+
'late': get_assignment_due_date(self.question.owner, self.question.assignment) < self.created,
436+
'text': self.response,
437+
}
438+
435439

436440
class Submission(db.Model):
437441
__tablename__ = "submission"
@@ -467,7 +471,7 @@ class Submission(db.Model):
467471
# Relationships
468472
owner = db.relationship(User)
469473
assignment = db.relationship(Assignment)
470-
build = db.relationship("SubmissionBuild", cascade="all,delete", backref='submission')
474+
build = db.relationship("SubmissionBuild", cascade="all,delete", uselist=False, backref='submission')
471475
test_results = db.relationship("SubmissionTestResult", cascade="all,delete", backref='submission')
472476
repo = db.relationship(AssignmentRepo, backref='submissions')
473477

@@ -658,7 +662,7 @@ class TheiaSession(db.Model):
658662
state = db.Column(db.TEXT)
659663
cluster_address = db.Column(db.TEXT, nullable=True, default=None)
660664
image = db.Column(
661-
db.TEXT, default="registry.osiris.services/anubis/theia-xv6"
665+
db.TEXT, default="registry.digitalocean.com/anubis/theia-xv6"
662666
)
663667
options = db.Column(MutableJson, nullable=False, default=lambda: dict())
664668
network_locked = db.Column(db.Boolean, default=True)
@@ -766,6 +770,3 @@ def data(self):
766770
'assignment_id': self.assignment_id,
767771
'due_date': str(self.due_date)
768772
}
769-
770-
771-

api/anubis/rpc/seed.py

+4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ def seed():
6262
intro_to_os_course = create_course(
6363
intro_to_os_students,
6464
name="Intro to OS", course_code="CS-UY 3224", section="A", professor="Gustavo",
65+
autograde_tests_repo='https://github.com/os3224/anubis-assignment-tests',
66+
github_org_url='https://github.com/os3224',
6567
)
6668
os_assignment, _, os_submissions, _ = create_assignment(intro_to_os_course, intro_to_os_students)
6769
init_submissions(os_submissions)
@@ -75,6 +77,8 @@ def seed():
7577
mmds_course = create_course(
7678
mmds_students,
7779
name="Mining Massive Datasets", course_code="CS-UY 3843", section="A", professor="Gustavo",
80+
autograde_tests_repo='https://github.com/os3224/anubis-assignment-tests',
81+
github_org_url='https://github.com/os3224'
7882
)
7983
mmds_assignment, _, mmds_submissions, _ = create_assignment(mmds_course, mmds_students)
8084
init_submissions(mmds_submissions)

api/anubis/rpc/theia.py

+49-23
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def create_theia_pod_obj(theia_session: TheiaSession):
5252
# Init container
5353
init_container = client.V1Container(
5454
name="theia-init-{}-{}".format(theia_session.owner.netid, theia_session.id),
55-
image="registry.osiris.services/anubis/theia-init:latest",
55+
image="registry.digitalocean.com/anubis/theia-init:latest",
5656
image_pull_policy=os.environ.get("IMAGE_PULL_POLICY", default="Always"),
5757
env=[
5858
client.V1EnvVar(name="GIT_REPO", value=theia_session.repo_url),
@@ -91,6 +91,14 @@ def create_theia_pod_obj(theia_session: TheiaSession):
9191
name='AUTOSAVE',
9292
value='ON' if autosave else 'OFF',
9393
),
94+
client.V1EnvVar(
95+
name='COURSE_ID',
96+
value=theia_session.course_id,
97+
),
98+
client.V1EnvVar(
99+
name='COURSE_CODE',
100+
value=theia_session.course.course_code,
101+
),
94102
*extra_env,
95103
],
96104
resources=client.V1ResourceRequirements(
@@ -111,29 +119,32 @@ def create_theia_pod_obj(theia_session: TheiaSession):
111119
containers.append(theia_container)
112120

113121
# Sidecar container
114-
if autosave:
115-
sidecar_container = client.V1Container(
116-
name="sidecar",
117-
image="registry.osiris.services/anubis/theia-sidecar:latest",
118-
image_pull_policy=os.environ.get("IMAGE_PULL_POLICY", default="Always"),
119-
env=[
120-
client.V1EnvVar(
121-
name="GIT_CRED",
122-
value_from=client.V1EnvVarSource(
123-
secret_key_ref=client.V1SecretKeySelector(
124-
name="git", key="credentials"
125-
)
126-
),
122+
sidecar_container = client.V1Container(
123+
name="sidecar",
124+
image="registry.digitalocean.com/anubis/theia-sidecar:latest",
125+
image_pull_policy=os.environ.get("IMAGE_PULL_POLICY", default="Always"),
126+
env=[
127+
client.V1EnvVar(
128+
name="GIT_CRED",
129+
value_from=client.V1EnvVarSource(
130+
secret_key_ref=client.V1SecretKeySelector(
131+
name="git", key="credentials"
132+
)
127133
),
128-
],
129-
volume_mounts=[
130-
client.V1VolumeMount(
131-
mount_path="/home/project",
132-
name=volume_name,
133-
)
134-
],
135-
)
136-
containers.append(sidecar_container)
134+
),
135+
client.V1EnvVar(
136+
name='AUTOSAVE',
137+
value='ON' if autosave else 'OFF',
138+
),
139+
],
140+
volume_mounts=[
141+
client.V1VolumeMount(
142+
mount_path="/home/project",
143+
name=volume_name,
144+
)
145+
],
146+
)
147+
containers.append(sidecar_container)
137148

138149
extra_labels = {}
139150
spec_extra = {}
@@ -238,13 +249,17 @@ def initialize_theia_session(theia_session_id: str):
238249
name = get_theia_pod_name(theia_session)
239250
n = 10
240251
while True:
252+
253+
# Get the pod information from the kubernetes api
241254
pod: client.V1Pod = v1.read_namespaced_pod(
242255
name=name,
243256
namespace="anubis",
244257
)
245258

246259
if pod.status.phase == "Pending":
247260
n += 1
261+
262+
# Wait at 60 iterations while it is pending before giving up
248263
if n > 60:
249264
logger.error(
250265
"Theia session took too long to initialize. Freeing worker."
@@ -254,8 +269,17 @@ def initialize_theia_session(theia_session_id: str):
254269
time.sleep(1)
255270

256271
if pod.status.phase == "Running":
272+
# Set the cluster address and state
257273
theia_session.cluster_address = pod.status.pod_ip
258274
theia_session.state = "Running"
275+
276+
# We need to introduce a small amount of time here
277+
# to give the theia server a moment to actually run
278+
# before the button on the web frontend to go to
279+
# the session is available to the user
280+
time.sleep(1)
281+
282+
# Index the event
259283
esindex(
260284
"theia",
261285
body={
@@ -264,6 +288,8 @@ def initialize_theia_session(theia_session_id: str):
264288
"netid": theia_session.owner.netid,
265289
},
266290
)
291+
292+
# Log the event
267293
logger.info("Theia session started {}".format(name))
268294
break
269295

api/anubis/utils/auth.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
from typing import Union
66

77
import jwt
8-
from flask import g
9-
from flask import request
8+
from flask import g, request, has_request_context
109

1110
from anubis.config import config
1211
from anubis.models import User, TAForCourse, ProfessorForCourse
@@ -74,6 +73,9 @@ def get_token() -> Union[str, None]:
7473
:return:
7574
"""
7675

76+
if not has_request_context():
77+
return None
78+
7779
return request.headers.get("token", default=None) or request.cookies.get(
7880
"token", default=None
7981
) or request.args.get('token', default=None)

api/anubis/utils/data.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,8 @@ def wrapper(*args, **kwargs):
326326

327327
# Push an app context
328328
with app.app_context():
329-
330-
# Call the function within an app context
331-
return function(*args, **kwargs)
329+
with app.test_request_context():
330+
# Call the function within an app context
331+
return function(*args, **kwargs)
332332

333333
return wrapper

api/anubis/utils/lms/assignments.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def assignment_sync(assignment_data: dict) -> Tuple[Union[dict, str], bool]:
153153
)
154154
).first()
155155
if course is None:
156-
return "Unable to find class", False
156+
return "Unable to find course", False
157157

158158
assert_course_admin(course.id)
159159

0 commit comments

Comments
 (0)