Skip to content
cayhorstmann edited this page Aug 27, 2019 · 4 revisions

What is LTIHub?

LTIHub aggregates hub-ready problems into LTI assignments.

An LTI assignment is an activity that can be assigned in any learning management system that supports the LTI protocol (Canvas, BlackBoard, Desire To Learn, Moodle, and so on).

A hub-ready problem is a CodeCheck problem or a "Horstmann Interactivity". In the future, more problem types can be made hub-ready. It is far easier to make a problem type hub-ready than to directly support the LTI standard.

The LTIHub allows an instructor to select one or more problems and make an assignment. Students can then use the learning management system to launch an LTIHub page that shows all problems in the assignment, and work on the problems, taking breaks as they please. When a student is done, the score is sent to the learning management system.

The aggregation is necessary since otherwise there would be a separate column for each individual problem. Imagine an introductory programming course with 5 short problems before each lecture. In a 15 week course, that's 30 assignments in the gradebook, which is still manageable. But if each problem had a separate column, there would be 150 columns, which is too unwieldy.

LTIHub was originally developed by Sunita Rajain and is described in her master's project report.

LTIHub Components

To run LTIHub, one needs

  • A virtual machine (Google Cloud or AWS) that runs the LTIHub Play application and supports HTTPS
  • A database (using a Google Cloud or AWS database engine)
  • A CodeCheck server that supports HTTPS
  • A static web site that serves "Horstmann Interactivities" using HTTPS

Building LTIHUb

Make a Google Cloud SQL database. Use PostgreSQL. Name lti-db1. Machine type db-g1-small (1.75 GB). Storage type HDD. Capacity 10GB. Pick a password. Choose defaults for everything else. Take note of the password and IP address. Create a database ltihub.

Use the SQL console to create the necessary tables.

\c ltihub

Then paste the Ups (not the Downs!!!) code from conf/evolutions/default/1.sql in the repo https://bitbucket.org/cayhorstmann/ltihub

Next, in the cloud environment, make a VM with Ubuntu 18.04. Call it lti-1.

Connect to it and run

cd /home/ubuntu
curl -sSO https://dl.google.com/cloudagents/install-logging-agent.sh
bash install-logging-agent.sh
rm install-logging-agent.sh

echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823
apt-get update
apt-get install wget unzip openjdk-8-jdk git sbt

sudo su ubuntu
git clone https://github.com/cayhorstmann/ltihub        
cd ltihub
sbt stage

We run this as the user ubuntu.

Make a file production.conf in the parent directory of the application (but use the actual database URL and password)

cat > /home/ubuntu/production.conf << "EOF"
include "application"
db {
  default.driver = org.postgresql.Driver
  default.url = "jdbc:postgresql://ipAddress/ltihub"
  default.username = root
  default.password = "dbpasswd"
}
play.crypto.secret="KMZBA<=;yvG35wsqkiRb93b8pz=pg[:0^OhXDM]fzzB^TCdk=ti4ys;JzT_hhFpq"
play.http.context=/lti
EOF 

Run these commands to turn the web app into a service:

sudo su -
cat > /lib/systemd/system/ltihub.service << "EOF"
[Unit]
Description=LTIHub
After=syslog.target network.target

[Service]
User=ubuntu
Group=adm

Type=simple
PIDFile=/home/ubuntu/ltihub/target/universal/stage/bin/RUNNING_PID
ExecStart=/home/ubuntu/ltihub/target/universal/stage/bin/ltihub -Dconfig.file=/home/ubuntu/production.conf -Dplay.http.context=/lti
ExecStop=/bin/kill $MAINPID
ExecStopPost=/bin/rm /home/ubuntu/ltihub/target/universal/stage/RUNNING_PID
TimeoutStartSec=600
Retart=on-failure

[Install]
WantedBy=multi-user.target
EOF
exit 

Add the IP address of the VM to the trusted database connections. (In the Cloud console, go to SQL, find the database, go to Connections, and add the public IP.

Then, run the following to start the service:

sudo systemctl --no-block start ltihub.service

and to stop it:

sudo systemctl stop ltihub.service

Now run

curl http://localhost:9000/lti/config

If you see an XML document, then this part is working.

If not, run

journalctl -xe

and study the log messages.

Configuring Google Cloud

Either install the gcloud tool on your laptop (https://cloud.google.com/sdk/install) or run the cloud shell (https://cloud.google.com/shell/docs/starting-cloud-shell).

## Define the following environment variables

export INSTANCE=lti-1
export REGION=us-central1
export ZONE=us-central1-a
export SERVICE=${INSTANCE}-service
export CHECK=${INSTANCE}-check
export GROUP=${INSTANCE}-group
export DEFAULT_SERVICE=${INSTANCE}-default-service
export MAP=${DEFAULT_SERVICE}-map
export MATCHER=${INSTANCE}-matcher

Be sure that the first four match the VM name, region, and zone chosen previously. Don't change the others.

Configure firewall

See if the firewall rule already exists:

gcloud compute firewall-rules list

Is there a rule default-allow-http-9000? If so, skip the next step. If not, run

gcloud compute firewall-rules create default-allow-http-9000 \
  --allow tcp:9000 \
  --source-ranges 0.0.0.0/0 \
  --description "Allow port 9000 access for all instances" \

At this point, you should be able to point your browser to (IP address):9000/lti/config and get the config page.

Configure instance group (with one instance)

gcloud compute instance-groups unmanaged create $GROUP --zone $ZONE
gcloud compute instance-groups unmanaged add-instances $GROUP --instances $INSTANCE --zone $ZONE
gcloud compute instance-groups managed set-named-ports $GROUP --named-ports http:9000 --zone $ZONE

Configure backends

gcloud compute http-health-checks create $CHECK \
  --port 9000 \
  --request-path /lti/count \
  --healthy-threshold 2 \
  --unhealthy-threshold 3 \
  --check-interval 10s

gcloud compute backend-services create $DEFAULT_SERVICE \
  --http-health-checks $CHECK \
  --global \
  --timeout 300s

gcloud compute backend-services create $SERVICE \
  --http-health-checks $CHECK \
  --global \
  --timeout 300s

gcloud compute backend-services add-backend $SERVICE \
  --instance-group $GROUP \
  --instance-group-zone $ZONE \
  --global

Configure load balancer

gcloud compute url-maps create $MAP \
      --default-service $DEFAULT_SERVICE 

gcloud compute url-maps add-path-matcher $MAP \
      --path-matcher-name $MATCHER \
      --default-service $DEFAULT_SERVICE \
      --path-rules "/lti/*=$SERVICE" 

gcloud compute target-http-proxies create $SERVICE-http-proxy \
  --url-map $MAP

Configure frontend

gcloud compute addresses create $SERVICE-address --global
gcloud compute addresses describe $SERVICE-address --global

export ADDRESS=...

gcloud compute forwarding-rules create $SERVICE-http-rule \
  --global \
  --address $ADDRESS \
  --target-http-proxy $SERVICE-http-proxy \
  --ports 80 

At this point, you should be able to point your browser to http://(frontent IP address)/lti/config and get the config page.

Configuring SSL

Install certbot as per https://certbot.eff.org/docs/install.html on your laptop.

Run

sudo ./certbot-auto certonly --manual --preferred-challenges dns

and follow the instructions about making a DNS record. You will get a file fullchain.pem and privkey.pem. Then

export CERTIFICATE=letsencrypt-`date +%Y-%m-%d`

gcloud compute ssl-certificates create $CERTIFICATE --certificate fullchain.pem --private-key privkey.pem

gcloud compute target-https-proxies create $SERVICE-https-proxy \
  --url-map $MAP \
  --ssl-certificate $CERTIFICATE

gcloud compute forwarding-rules create $SERVICE-https-rule \
  --global \
  --address $ADDRESS \
  --target-https-proxy $SERVICE-https-proxy \
  --ports 443

Now visit https://(frontent IP address)/lti/config and check that you get the config page.

When you need to renew the certificate upon expiration, run

export CERTIFICATE=letsencrypt-`date +%Y-%m-%d`
gcloud compute target-https-proxies update service1-https-proxy --ssl-certificates $CERTIFICATE

If you need to clean up...

Only do this if things went wrong and you need to clean up. Start with the last thing you created.

gcloud compute forwarding-rules delete $SERVICE-https-rule
gcloud compute target-https-proxies delete $SERVICE-https-proxy
gcloud compute ssl-certificates delete $CERTIFICATE
gcloud compute addresses delete $SERVICE-address --global
gcloud compute target-http-proxies delete $SERVICE-http-proxy
gcloud compute url-maps delete $MAP
gcloud compute backend-services delete $SERVICE --global
gcloud compute backend-services delete $DEFAULT_SERVICE --global       
gcloud compute http-health-checks delete $CHECK                         
gcloud compute instance-groups unmanaged delete $GROUP --zone $ZONE

TODO Configuring Canvas

Use this XML file to configure the app.

<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
    xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
    xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
    xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
    xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd
    http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd
    http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd
    http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">
    <blti:title>LTIHub</blti:title>
    <blti:description>An app to take care of LTI configuration</blti:description>
    <blti:launch_url>https://play.codecheck.ws/lti/assignment</blti:launch_url>
    <blti:extensions platform="canvas.instructure.com">
      <lticm:property name="privacy_level">public</lticm:property>
      <lticm:property name="domain">https://play.codecheck.ws/lti/addAssignment</lticm:property>
      <lticm:options name="resource_selection">
        <lticm:property name="enabled">true</lticm:property>
        <lticm:property name="url">https://play.codecheck.ws/lti/createAssignment</lticm:property>
        <lticm:property name="text">Exercise Selector</lticm:property>
        <lticm:property name="selection_width">800</lticm:property>
        <lticm:property name="selection_height">600</lticm:property>
      </lticm:options>
    </blti:extensions>
</cartridge_basiclti_link>