Skip to content
This repository has been archived by the owner on Sep 5, 2023. It is now read-only.

lti changes #644

Open
wants to merge 61 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
7ec5396
lti changes
rupeshparab Mar 21, 2022
ee0dfd7
added illumidesk secret option
Abhi94N Mar 22, 2022
d07623c
Dockerfile changes
rupeshparab Mar 22, 2022
f30091c
add env variable
rupeshparab Mar 22, 2022
32db3a5
initialization of secrets manager
Abhi94N Mar 25, 2022
070ca4f
updated graderservice to add restart grader functionality
Abhi94N Mar 25, 2022
4554626
updated route to restart grader
Abhi94N Mar 25, 2022
2ad280f
added response code for route
Abhi94N Mar 25, 2022
938df6a
updated formgrader secretsmanager version to a valid one
Abhi94N Mar 25, 2022
b8944e5
graderservice restart grader timeout 10 seconds
Abhi94N Mar 26, 2022
48a6467
grader service sleep changed
Abhi94N Mar 29, 2022
7ac98ac
updated to jupyterhub ltiauthenticator
Abhi94N Mar 29, 2022
abe0606
updated jinja2 and markupsafe version
Abhi94N Mar 29, 2022
47bf382
remove markup package
Abhi94N Mar 29, 2022
089e1c3
set jinja version to 3.0.3
Abhi94N Mar 29, 2022
508d95d
removed markup safe
Abhi94N Mar 29, 2022
28d73e9
updated jinja2 and packages dependent on jinja2
Abhi94N Mar 29, 2022
96390e1
fixed jinja version
Abhi94N Mar 29, 2022
6a7c7a1
update jedi version
Abhi94N Mar 29, 2022
5bfdada
jinja 3.1.1
Abhi94N Mar 29, 2022
0216198
oauthlib 3.1.1
Abhi94N Mar 29, 2022
40f79ca
revert jinja2 to 3.0.3
Abhi94N Mar 29, 2022
980b972
updated flask to version 2.1.0
Abhi94N Mar 29, 2022
cee4464
revert flask version
Abhi94N Mar 29, 2022
e2aceec
flask version 1.1.1
Abhi94N Mar 29, 2022
db33df4
update requirements
Abhi94N Mar 29, 2022
784dd48
update requirements
Abhi94N Mar 29, 2022
5d3a3f4
update test location in makefile
Abhi94N Mar 29, 2022
8a4c151
removed asyncnbgrader test
Abhi94N Apr 1, 2022
972352e
add async_test back
Abhi94N Apr 3, 2022
aa9d7da
uses secretmanager package to fetch secret values
Abhi94N Apr 7, 2022
e1fdbb5
fix role addition issue
rupeshparab Apr 12, 2022
98d951e
update markup safe
Abhi94N Apr 12, 2022
29cb562
update jinja2 version
Abhi94N Apr 12, 2022
fb09de6
updated flask to version 2.1.0
Abhi94N Apr 12, 2022
0405223
updated jinja2
Abhi94N Apr 12, 2022
e2fb9a7
update jinja2 for async_nbgrader
Abhi94N Apr 12, 2022
92db213
updated jinja2 to semver 2.10>=jinja2<3.1.0
Abhi94N Apr 12, 2022
56d5cbb
updated nbconvert version
Abhi94N Apr 12, 2022
9e6533a
jinja2 version downgrade to 3.1.0
Abhi94N Apr 12, 2022
7ba436a
updated flask to 2.1.0
Abhi94N Apr 12, 2022
13d4ad2
revert nbconvert
Abhi94N Apr 12, 2022
c365961
update werkzeug
Abhi94N Apr 12, 2022
a4691ea
update jinja2 to the latest version
Abhi94N Apr 12, 2022
a533e56
jinja 3.1
Abhi94N Apr 12, 2022
08463b3
nbconvert version 6.4.3
Abhi94N Apr 12, 2022
9da1fb6
reverted requirements txt
Abhi94N Apr 12, 2022
7c53d06
updates ltiauthenticator to use pypi version
Abhi94N Apr 12, 2022
83856ec
matched kubernetes version
Abhi94N Apr 12, 2022
b336d3f
update requirements.txt
Abhi94N Apr 12, 2022
a39211c
fix build
rupeshparab Apr 14, 2022
819dbb7
updated graderservice.py
Abhi94N Apr 14, 2022
6f05a41
updated secrets manager with restart grader route
Abhi94N Apr 14, 2022
839a1a8
tests for secrets and rollow restart route
Abhi94N Apr 14, 2022
d29cbd7
merge secrets pr to include secretsarn
Abhi94N Apr 15, 2022
17ea6c3
add default course hook for auth0
rupeshparab Apr 20, 2022
1d8a951
Merge branch 'lti_changes' of github.com:IllumiDesk/illumidesk into l…
rupeshparab Apr 20, 2022
9fbb70e
fix requirements
rupeshparab Apr 20, 2022
1351cc1
update default course name
rupeshparab Apr 21, 2022
6c0ce3f
add user to default course
rupeshparab Apr 22, 2022
3abcc7f
deployment fixes
rupeshparab May 5, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions src/graderservice/graderservice/graderservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@
"ILLUMIDESK_NB_EXCHANGE_MNT_ROOT", "/illumidesk-nb-exchange"
)
GRADER_PVC = os.environ.get("GRADER_PVC", "grader-setup-pvc")
GRADER_EXCHANGE_SHARED_PVC = os.environ.get(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has the removal of GRADER_EXCHANGE_SHARED_PVC been tested? I was still having issues after I removed the deployment. That being said I was testing it without efs access points as we didn't need that in the Oregon cluster. I can try again with a new image and approve this afterward. Let me know if you have a new image, if not I'll fetch this PR and create the image and once tested, approve this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have created a new image of this with pr-71 tag

"GRADER_SHARED_PVC", "exchange-shared-volume"
)

# user UI and GID to use within the grader container
NB_UID = os.environ.get("NB_UID", 10001)
Expand All @@ -53,6 +50,8 @@
JUPYTERHUB_API_URL = os.environ.get("JUPYTERHUB_API_URL") or "http://hub:8081/hub/api"
JUPYTERHUB_BASE_URL = os.environ.get("JUPYTERHUB_BASE_URL") or "/"

CAMPUS_ID = os.environ.get("CAMPUS_ID")

# NBGrader database settings to save in nbgrader_config.py file
nbgrader_db_host = os.environ.get("POSTGRES_NBGRADER_HOST")
nbgrader_db_password = os.environ.get("POSTGRES_NBGRADER_PASSWORD")
Expand Down Expand Up @@ -184,7 +183,7 @@ def _create_nbgrader_files(self):
grader_home_nbconfig_content = NBGRADER_HOME_CONFIG_TEMPLATE.format(
grader_name=self.grader_name,
course_id=self.course_id,
db_url=f"postgresql://{nbgrader_db_user}:{nbgrader_db_password}@{nbgrader_db_host}:5432/{self.org_name}_{self.course_id}",
db_url=f"postgresql://{nbgrader_db_user}:{nbgrader_db_password}@{nbgrader_db_host}:5432/{nbgrader_db_name}",
)
grader_nbconfig_path.write_text(grader_home_nbconfig_content)
# Write the nbgrader_config.py file at grader home directory
Expand Down Expand Up @@ -269,6 +268,7 @@ def _create_deployment_object(self):
client.V1EnvVar(name="NB_UID", value=str(NB_UID)),
client.V1EnvVar(name="NB_GID", value=str(NB_GID)),
client.V1EnvVar(name="NB_USER", value=self.grader_name),
client.V1EnvVar(name="CAMPUS_ID", value=str(CAMPUS_ID)),
],
volume_mounts=[
client.V1VolumeMount(
Expand All @@ -278,7 +278,7 @@ def _create_deployment_object(self):
),
client.V1VolumeMount(
mount_path="/srv/nbgrader/exchange",
name=GRADER_EXCHANGE_SHARED_PVC,
name=GRADER_PVC,
sub_path=sub_path_exchange,
),
],
Expand All @@ -298,12 +298,6 @@ def _create_deployment_object(self):
claim_name=GRADER_PVC
),
),
client.V1Volume(
name=GRADER_EXCHANGE_SHARED_PVC,
persistent_volume_claim=client.V1PersistentVolumeClaimVolumeSource(
claim_name=GRADER_EXCHANGE_SHARED_PVC
),
),
],
),
)
Expand Down
43 changes: 40 additions & 3 deletions src/illumidesk/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,37 +1,74 @@
# for kubernetes, use the --build-arg when building image or uncomment
# ARG BASE_IMAGE=jupyterhub/k8s-hub:1.1.2
ARG BASE_IMAGE=jupyterhub/jupyterhub:1.4.2
ARG SSH_PRIVATE_KEY
FROM "${BASE_IMAGE}"

USER root

RUN apt-get update \
&& apt-get install -y \
curl \
git \
unzip \
wget \
openssh-server \
libmysqlclient-dev \
&& rm -rf /var/lib/apt/lists/*

USER "${NB_USER}"

ARG SSH_PRIVATE_KEY
RUN mkdir ~/.ssh/
RUN echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_ed25519
RUN chmod 600 ~/.ssh/id_ed25519
RUN ssh-keyscan github.com >> ~/.ssh/known_hosts
# Print SSH_PRIVATE_KEY (for test)
RUN echo "${SSH_PRIVATE_KEY}"

# # Authorize SSH Host
# RUN mkdir -p "/home/${NB_USER}/.ssh" && \
# chmod 0700 "/home/${NB_USER}/.ssh"

# COPY ./id_illumidesk_ssh "/home/${NB_USER}/.ssh/id_rsa"
# COPY ./id_illumidesk_ssh.pub "/home/${NB_USER}/.ssh/id_rsa.pub"

# # Add the keys and set permissions
# RUN chmod 600 "/home/${NB_USER}/.ssh/id_rsa" && \
# chmod 600 "/home/${NB_USER}/.ssh/id_rsa.pub" && \
# touch "/home/${NB_USER}/.ssh"/known_hosts

# RUN chown -R "${NB_USER}" "/home/${NB_USER}/.ssh"

# # RUN ssh-keyscan github.com >> "/home/${NB_USER}/.ssh"/known_hosts
# RUN ssh-keyscan -t ssh-ed25519 github.com >> "/home/${NB_USER}/.ssh"/known_hosts

# RUN file="/home/${NB_USER}/.ssh"/known_hosts && echo $file

# # RUN echo "Host github.com\n\tStrictHostKeyChecking no\n" >> "/home/${NB_USER}/.ssh/config"
# RUN file="/home/${NB_USER}/.ssh/config" && echo $file

WORKDIR /tmp
RUN wget https://configs.illumidesk.com/images/illumidesk-80.png \
&& cp -r /tmp/illumidesk-80.png /srv/jupyterhub/ \
&& cp -r /tmp/illumidesk-80.png /usr/local/share/jupyterhub/static/images/illumidesk-80.png \
&& chown "${NB_UID}" /srv/jupyterhub/illumidesk-80.png

USER "${NB_UID}"

ENV PATH="/home/${NB_USER}/.local/bin:${PATH}"
# ENV PYTHONUNBUFFERED 1

# ensure pip is up to date
RUN python3 -m pip install --upgrade pip

COPY requirements.txt /tmp/
RUN pip install -r /tmp/requirements.txt
RUN pip install -r /tmp/requirements.txt --use-deprecated=legacy-resolver

WORKDIR /tmp
COPY . /tmp
RUN python3 -m pip install /tmp/.

RUN rm -rf "/home/${NB_USER}/.ssh/"

WORKDIR /srv/jupyterhub/

# This config is overwitten with k8s setup
Expand Down
63 changes: 26 additions & 37 deletions src/illumidesk/illumidesk/apis/nbgrader_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from nbgrader.api import Course
from nbgrader.api import Gradebook
from nbgrader.api import InvalidEntry
from sqlalchemy_utils import create_database
from sqlalchemy_utils import database_exists

from illumidesk.authenticators.utils import LTIUtils

Expand All @@ -18,24 +16,23 @@
nbgrader_db_port = os.environ.get("POSTGRES_NBGRADER_PORT") or 5432
nbgrader_db_password = os.environ.get("POSTGRES_NBGRADER_PASSWORD")
nbgrader_db_user = os.environ.get("POSTGRES_NBGRADER_USER")
nbgrader_db_name = os.environ.get("POSTGRES_NBGRADER_DATABASE")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If database does not exist, I am getting the following error

[W 2022-04-01 23:08:55.292 JupyterHub base:1256] Rolling back session due to database error (psycopg2.OperationalError) FATAL: database "tea_course" does not exist

mnt_root = os.environ.get("ILLUMIDESK_MNT_ROOT", "/illumidesk-courses")

org_name = os.environ.get("ORGANIZATION_NAME") or "my-org"

if not org_name:
raise EnvironmentError("ORGANIZATION_NAME env-var is not set")

CAMPUS_ID = os.environ.get("CAMPUS_ID")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the database does exist in rds instance like in the case of illumidesk_next_20220201 database, I get the following error

Rolling back session due to database error (psycopg2.errors.UndefinedColumn) column course.campus_id does not exist LINE 1: ...urse.id AS course_id, course.name AS course_name, course.cam..

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the database does exist in rds instance like in the case of illumidesk_next_20220201 database, I get the following error

Rolling back session due to database error (psycopg2.errors.UndefinedColumn) column course.campus_id does not exist LINE 1: ...urse.id AS course_id, course.name AS course_name, course.cam..

if not CAMPUS_ID:
raise EnvironmentError("CAMPUS_ID env-var is not set")

def nbgrader_format_db_url(course_id: str) -> str:
def nbgrader_format_db_url() -> str:
"""
Returns the nbgrader database url with the format: <org_name>_<course-id>

Args:
course_id: the course id (usually associated with the course label) from which the launch was initiated.
Returns the nbgrader database url
"""
course_id = LTIUtils().normalize_string(course_id)
database_name = f"{org_name}_{course_id}"
return f"postgresql://{nbgrader_db_user}:{nbgrader_db_password}@{nbgrader_db_host}:{nbgrader_db_port}/{database_name}"
return f"postgresql://{nbgrader_db_user}:{nbgrader_db_password}@{nbgrader_db_host}:{nbgrader_db_port}/{nbgrader_db_name}"


class NbGraderServiceHelper:
Expand All @@ -51,7 +48,7 @@ class NbGraderServiceHelper:
database_name: the database name
"""

def __init__(self, course_id: str, check_database_exists: bool = False):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what I'm understanding here is that all courses will be in one database instead of a database-specific for each org_course. This assumes that the database exists by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, the DB will be common and will be maintained by the migrations in admin-dashboard code base

Copy link

@Abhi94N Abhi94N Apr 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we may need to add back the check_databse_exist boolean value in case the database does not exist

def __init__(self, course_id: str):
if not course_id:
raise ValueError("course_id missing")

Expand All @@ -62,56 +59,48 @@ def __init__(self, course_id: str, check_database_exists: bool = False):
self.uid = int(os.environ.get("NB_GRADER_UID") or "10001")
self.gid = int(os.environ.get("NB_GRADER_GID") or "100")

self.db_url = nbgrader_format_db_url(course_id)
self.database_name = f"{org_name}_{self.course_id}"
if check_database_exists:
self.create_database_if_not_exists()

def create_database_if_not_exists(self) -> None:
"""Creates a new database if it doesn't exist"""
conn_uri = nbgrader_format_db_url(self.course_id)

if not database_exists(conn_uri):
logger.debug("db not exist, create database")
create_database(conn_uri)
self.db_url = nbgrader_format_db_url()

def add_user_to_nbgrader_gradebook(self, username: str, lms_user_id: str) -> None:
def add_user_to_nbgrader_gradebook(self, email: str, external_user_id: str, source: str, source_type: str, role: str) -> None:
"""
Adds a user to the nbgrader gradebook database for the course.

Args:
username: The user's username
lms_user_id: The user's id on the LMS
email: The user's email
external_user_id: The user's id on the external system
source: source from where user was authenticated
source_type: source_type
Raises:
InvalidEntry: when there was an error adding the user to the database
"""
if not username:
raise ValueError("username missing")
if not lms_user_id:
raise ValueError("lms_user_id missing")
if not email:
raise ValueError("email missing")
if not external_user_id:
raise ValueError("external_user_id missing")

with Gradebook(self.db_url, course_id=self.course_id) as gb:
with Gradebook(self.db_url, course_id=self.course_id, campus_id=CAMPUS_ID) as gb:
try:
gb.update_or_create_student(username, lms_user_id=lms_user_id)
user = gb.update_or_create_user_by_email(email, role=role, external_user_id=external_user_id, source=source, source_type=source_type)
logger.debug(
"Added user %s with lms_user_id %s to gradebook"
% (username, lms_user_id)
"Added user %s with external_user_id %s to gradebook"
% (email, external_user_id)
)
return user.to_dict()
except InvalidEntry as e:
logger.debug("Error during adding student to gradebook: %s" % e)

def update_course(self, **kwargs) -> None:
"""
Updates the course in nbgrader database
"""
with Gradebook(self.db_url, course_id=self.course_id) as gb:
with Gradebook(self.db_url, course_id=self.course_id, campus_id=CAMPUS_ID) as gb:
gb.update_course(self.course_id, **kwargs)

def get_course(self) -> Course:
"""
Gets the course model instance
"""
with Gradebook(self.db_url, course_id=self.course_id) as gb:
with Gradebook(self.db_url, course_id=self.course_id, campus_id=CAMPUS_ID) as gb:
course = gb.check_course(self.course_id)
logger.debug(f"course got from db:{course}")
return course
Expand All @@ -131,7 +120,7 @@ def register_assignment(self, assignment_name: str, **kwargs: dict) -> Assignmen
"Assignment name normalized %s to save in gradebook" % assignment_name
)
assignment = None
with Gradebook(self.db_url, course_id=self.course_id) as gb:
with Gradebook(self.db_url, course_id=self.course_id, campus_id=CAMPUS_ID) as gb:
try:
assignment = gb.update_or_create_assignment(assignment_name, **kwargs)
logger.debug("Added assignment %s to gradebook" % assignment_name)
Expand Down
Loading