UNCLASSIFIED

Commit 598f47fa authored by Andy Maksymowicz's avatar Andy Maksymowicz
Browse files

Merge branch 'development' into 'master'

Development

See merge request !4
parents a38be89f 4442f637
Pipeline #192935 failed with stages
in 7 minutes and 52 seconds
.DS_Store
.idea
__pycache__
old
json-object.json
main.py
test_methods.py
results.txt
mike-run-local-loop.bash
*-local-loop.sh
build_dependencies
\ No newline at end of file
FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG}
USER 0
# Make sure we are up to date.
RUN dnf update -y && dnf clean all
# Copy in the project files.
WORKDIR /app
COPY ./scripts .
COPY redis-cli.tar.gz .
# Install redis.
RUN tar xzf redis-cli.tar.gz && rm redis-cli.tar.gz
RUN mv redis-cli /usr/local/bin/redis-cli
# Set permissions and ownership.
RUN chown -R 1001 /app
RUN chmod 0555 /app/get-job.sh
# Become the python user.
USER 1001
# Health check.
HEALTHCHECK none
# Set the entrypoint.
ENTRYPOINT ["/app/get-job.sh"]
\ No newline at end of file
Copyright 2021. Galvanize Inc. Galvanize Inc. proprietary and confidential. Do not distribute.
A commercial license will be required to run an instance (or any copy or derivative thereof) of this software and the
learning management system (the "Software"). Purchase of a license will be facilitated by contacting
legal@galvanize.com. Use of the Software is prohibited without a commercial license. By way of example, but not
limitation, without a commercial license, you may not use the Software in any way or at any time, either directly or
indirectly, including but not limited to, you may not at any time, attempt, directly or indirectly, or allow any third
party to attempt to (i) copy, modify, or create derivative works of the Software, in whole or in part; (ii) use, rent,
lease, lend, sell, license, sublicense, assign, distribute, publish, transfer, or otherwise make available the Software;
(iii) reverse engineer, disassemble, decompile, decode, adapt, or otherwise attempt to derive or gain access to any
software component of the Software, in whole or in part; (iv) remove any proprietary notices from the Software;
or (v) use the Software in any manner or for any purpose that infringes, misappropriates, or otherwise violates any
intellectual property right or other right of any person, or that violates any applicable law. THE SOFTWARE IS PROVIDED
"AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL GALVANIZE, THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
FURTHER, IN NO EVENT WILL GALVANIZE, THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE UNDER OR IN CONNECTION WITH THIS
AGREEMENT UNDER ANY LEGAL OR EQUITABLE THEORY, INCLUDING BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT
LIABILITY, AND OTHERWISE, FOR ANY: (a) CONSEQUENTIAL, INCIDENTAL, INDIRECT, EXEMPLARY, SPECIAL, ENHANCED, OR PUNITIVE
DAMAGES; (b) INCREASED COSTS, DIMINUTION IN VALUE OR LOST BUSINESS, PRODUCTION, REVENUES, OR PROFITS; (c) LOSS OF
GOODWILL OR REPUTATION; (d) USE, INABILITY TO USE, LOSS, INTERRUPTION, DELAY OR RECOVERY OF ANY DATA, OR BREACH OF
DATA OR SYSTEM SECURITY; (e) COST OF REPLACEMENT GOODS OR SERVICES; OR (f) ANY DIRECT DAMAGES, INCLUDING ANY LIABILITY
ARISING OUT OF OR RELATED TO THIS AGREEMENT UNDER ANY LEGAL OR EQUITABLE THEORY, INCLUDING BREACH OF CONTRACT,
TORT (INCLUDING NEGLIGENCE), OR STRICT LIABILITY, IN EACH CASE REGARDLESS OF WHETHER GALVANIZE, THE AUTHORS OR
COPYRIGHT HOLDERS WAS ADVISED OF THE POSSIBILITY OF SUCH LOSSES OR DAMAGES OR SUCH LOSSES OR DAMAGES WERE OTHERWISE
FORESEEABLE.
\ No newline at end of file
# python-code-evaluator
Python Evaluator
====================
python-code-evaluator
\ No newline at end of file
## Description
Python code evaluator for Galvanize LEARN. This service picks up jobs from a redis queue and evaluates Python
code snippets that were submitted from students through the LEARN application.
## Requirements:
- Redis CLI
- Python 3
## Container Usage:
### Container info:
- workdir: /usr/src/app
- user id: 1001
- Does not require container port mapping.
### Recommended Resources:
- Min/Max CPU: 0.1/0.2
- Min/Max Memory: 64Mi/128Mi
### Required Environment Variables:
- ```REDIS_HOST``` - The redis server host address.
## Local Development Setup
1. Install redis. Skip this if you already have it installed and running.
```
brew install redis
brew services start redis
```
2. Install Python 3:
```
brew install python
```
3. Run the bash file to pick up jobs from redis:
```
cd scripts
./run-local-loop.sh
```
\ No newline at end of file
---
apiVersion: v1
# The repository name in registry1, excluding /ironbank/
name: "galvanize/galvanize/python-code-evaluator"
# List of tags to push for the repository in registry1
# The most specific version should be the first tag and will be shown
# on ironbank.dsop.io
tags:
- "0.1.0"
- "latest"
# Build args passed to Dockerfile ARGs
args:
BASE_IMAGE: "opensource/python/python38"
BASE_TAG: "3.8"
# Docker image labels
labels:
org.opencontainers.image.title: "python-code-evaluator"
org.opencontainers.image.description: "The python-code-evaluator is a microservice that supports the Galvanize Learn application. Its purpose is to process python code snippets to determine if student quiz submissions correctly answer programming code challenges defined by instructors in the Learn application. Code evaluation jobs are passed from forge, the main learn application, to a specific redis queue where they are picked up by the python-code-evaluator. The redis job contains the student code submission and an instructor defined python unit test that is used to determine whether or not the code returns the correct value. When the python-code-evaluator finishes, it POSTs its result back to forge which displays the result to the student."
org.opencontainers.image.licenses: "proprietary"
org.opencontainers.image.url: "https://www.galvanize.com"
org.opencontainers.image.vendor: "Galvanize"
org.opencontainers.image.version: "3.8"
mil.dso.ironbank.image.keywords: "lms,learn,galvanize,online,classes,remote,learning"
mil.dso.ironbank.image.type: "commercial"
mil.dso.ironbank.product.name: "Learn"
# List of resources to make available to the offline build context
resources:
- auth:
type: s3
id: galvanize
region: us-gov-west-1
url: s3://learn-dependencies/python-evaluator/redis-cli.tar.gz
filename: redis-cli.tar.gz
validation:
type: sha256
value: 1a4a61818d33d8f9c9aea0f3ce1ed3093500f25ac750365ae6c06a6d2f0ee123
# List of project maintainers
maintainers:
- email: "dchong@revacomm.com"
name: "Derrin Chong"
username: "dchong"
- email: "muranaka@revacomm.com"
name: "Michael Uranaka"
username: "muranaka"
- email: "csakamaki@revacomm.com"
name: "Charlie Sakamaki"
username: "csakamaki"
- name: "Joshua Eason"
username: "jeason"
cht_member: true
ARG BASE_REGISTRY
ARG BASE_IMAGE
ARG BASE_TAG
FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG}
USER 0
ARG NODE_VERSION
ARG YARN_VERSION
ARG JQUERY_RAILS_VERSION
# Install required libs.
RUN dnf update -y && dnf install -y \
curl \
make \
gcc \
gcc-c++
# Setup our environment
WORKDIR /app
# Redis
RUN curl -L http://download.redis.io/redis-stable.tar.gz -o redis-stable.tar.gz
RUN tar xzf redis-stable.tar.gz
WORKDIR /app/redis-stable
RUN make redis-cli
WORKDIR /app/redis-stable/src
RUN tar czf redis-cli.tar.gz redis-cli
RUN mv redis-cli.tar.gz /app/
# Switch back to app dir.
WORKDIR /app
# Add write permissions.
RUN chown -R 1001 .
# Set the entry point.
CMD tail -f /dev/null
import os
DATA_FILE_PATH = "json-object.json"
OUTPUT_FILE_NAME = "test_methods.py"
OUTPUT_FILE_PATH = os.path.join('./', OUTPUT_FILE_NAME)
CODE_FILE_NAME = "main.py"
CODE_FILE_PATH = os.path.join('./', CODE_FILE_NAME)
RESULTS_FILE = "results.txt"
ERROR_INDICATOR = "FAILED"
#!/bin/sh
loop=1
while true; do
# Start fresh, recopy the app into the working directory
rm -rf /usr/src/app
cp -r /app /usr/src
cd /usr/src/app
# Connect to the redis service,
# apply the redis commands to block-pop
# grep out the actual job payload, and
# write it to the json-object.json file
redis-cli -h $REDIS_HOST < redis-cmds | \
grep 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper' > \
json-object.json
# continue if the json file is empty.
[ -s json-object.json ] || continue
# Log the current job number.
echo
echo "============================"
echo "Processing job number: $loop"
echo "============================"
echo
let "loop=loop+1"
# Do some pre-processing
python process-job.py
# Run the evaluator
python -m unittest discover 2>&1 | tee results.txt
# Send back the results
python send-results.py
done
from config import DATA_FILE_PATH, CODE_FILE_PATH, OUTPUT_FILE_PATH
from utilities import file_utils
job_args = file_utils.get_job_args(DATA_FILE_PATH)
file_utils.write_file(CODE_FILE_PATH, job_args['code_to_assess'])
# Build the test file data
file_data = """
{setup}
{tests}
""".format(setup=job_args['setup'],
tests=job_args['tests_to_assess_against'])
# write the output file data
file_utils.write_file(OUTPUT_FILE_PATH, file_data)
SELECT 0
BLPOP queue:python_evaluation 0
#!/bin/bash
# Image Params.
BASE_REGISTRY=registry.il2.dso.mil
BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/python38
BASE_TAG=3.8.31
# This is the profile name in your aws credentials file.
DEPENDENCY_FOLDER=build_dependencies
AWS_PROFILE_NAME=revacomm
AWS_BUCKET_NAME=learn-dependencies/python-evaluator
AWS_REGION=us-gov-west-1
echo "Clearing dependency folder."
rm -rf $DEPENDENCY_FOLDER
mkdir $DEPENDENCY_FOLDER
echo "Building docker image."
IMAGE_ID=$(docker build --file Dockerfile.packages . -q \
--build-arg BASE_REGISTRY=$BASE_REGISTRY \
--build-arg BASE_IMAGE=$BASE_IMAGE \
--build-arg BASE_TAG=$BASE_TAG)
echo "Image ID: ${IMAGE_ID}"
echo "Starting docker container."
CONTAINER_ID=$(docker run -d "$IMAGE_ID")
echo "Container ID: ${CONTAINER_ID}"
echo "Copying node modules and gems to the bundles directory."
docker cp "$CONTAINER_ID":/app/redis-cli.tar.gz $DEPENDENCY_FOLDER
echo "Stopping the docker container."
docker stop "$CONTAINER_ID"
echo "Uploading all the build dependencies to AWS."
aws s3 sync $DEPENDENCY_FOLDER s3://$AWS_BUCKET_NAME --delete --profile $AWS_PROFILE_NAME --region $AWS_REGION
echo "Checksums:"
cd $DEPENDENCY_FOLDER
sha256sum redis-cli.tar.gz
cd ..
echo "Removing dependency folder."
rm -rf $DEPENDENCY_FOLDER
echo "Done!"
\ No newline at end of file
#!/bin/sh
while true; do
# If it exists, delete the json-object.json file and test folder
[ -e test_methods.py ] && rm test_methods.py
[ -e results.txt ] && rm results.txt
[ -e json-object.json ] && rm json-object.json
# Connect to the redis service,
# apply the redis commands to block-pop
# grep out the actual job payload, and
# write it to the json-object.json file
redis-cli -h localhost < redis-cmds | \
grep 'ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper' > \
json-object.json
# continue if the json file is empty.
[ -s json-object.json ] || continue
cat json-object.json
# Do some pre-processing
python process-job.py
# Run the evaluator
python -m unittest discover 2>&1 | tee results.txt
# Send back the results
python send-results.py
done
import sys
from config import DATA_FILE_PATH, RESULTS_FILE
from utilities import file_utils
from services import results_service
# Get job args
job_args = file_utils.get_job_args(DATA_FILE_PATH)
# Get the callback url
if not job_args['callback_url'] or len(job_args['callback_url'].strip()) == 0:
sys.exit('Callback URL is missing.')
# get the results data.
results_data = results_service.build_results_data(RESULTS_FILE)
if not results_data:
sys.exit('Failed to generate result data.')
# Fire the request
results_service.send_results(results_data, job_args['callback_url'])
# Gracefully exit
sys.exit(0)
import json
import urllib.request
from config import ERROR_INDICATOR
from utilities import file_utils
from utilities import time_utils
def build_results_data(results_file_path):
# Get the results data
results_data = file_utils.read_file(results_file_path)
# Get the status
status = 'correct'
if results_data.find(ERROR_INDICATOR) != -1:
status = 'incorrect'
return {
"results": results_data,
"status": status,
"assessments_ran_at": time_utils.get_current_time_str()
}
def send_results(results, url):
params = json.dumps(results).encode('utf8')
req = urllib.request.Request(
url,
data=params,
headers={'content-type': 'application/json'}
)
req.get_method = lambda: 'PATCH'
urllib.request.urlopen(req)
import os
import sys
import json
def get_job_args(file_path):
# make sure the JSON data file exists.
if not os.path.exists(file_path):
sys.exit("JSON data file is missing.")
# read the json file.
try:
with open(file_path) as raw_data:
job = json.load(raw_data)
# get the job arguments.
return job['args'][0]['arguments'][0]
except IOError as e:
sys.exit("Failed to get job arguments: " + str(e))
def write_file(file_path, data):
try:
filehandle = open(file_path, 'w')
filehandle.write(data)
filehandle.close()
except IOError as e:
sys.exit("Failed to write file: " + str(e))
def read_file(file_path):
try:
f = open(file_path, "r")
data = f.read()
f.close()
return data
except IOError as e:
sys.exit("Failed to read file: " + str(e))
from datetime import datetime
def get_current_time_str():
# Get current date
now = datetime.utcnow()
return now.isoformat(' ', 'seconds') + ' UTC'
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment