diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6a874f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.*.swp +!.gitignore +TODO +__pycache__ +*.egg-info/ +/build/ +/dist/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..8493bfa --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,40 @@ +build:image: + script: + - apk add --no-cache docker + - python setup.py install + - tutor plugins enable xqueue + - tutor config save + - tutor images build xqueue + only: + refs: + - master + tags: + - private + stage: build + +deploy:image: + script: + - apk add --no-cache docker + - docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD" + - python setup.py install + - tutor plugins enable xqueue + - tutor config save + - tutor images push xqueue + only: + refs: + - master + tags: + - private + stage: deploy + +deploy:pypi: + script: + - pip3 install -U setuptools twine + - python3 setup.py sdist + - twine upload --skip-existing dist/tutor-xqueue*.tar.gz + only: + refs: + - master + tags: + - private + stage: deploy diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..07e95d4 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +recursive-include tutorxqueue/patches * +recursive-include tutorxqueue/templates * \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..b9888d2 --- /dev/null +++ b/README.rst @@ -0,0 +1,28 @@ +Xqueue external grading system plugin for `Tutor `_ +=================================================================================== + +This is a plugin for `Tutor `_ that provides the Xqueue external grading system for Open edX platforms. If you don't know what it is, you probably don't need it. + +Installation +------------ + +The plugin is currently bundled with the `binary releases of Tutor `_. If you have installed Tutor from source, you will have to install this plugin from source, too:: + + pip install tutor-xqueue + +Then, to enable this plugin, run:: + + tutor plugins enable xqueue + +Configuration +------------- + +- ``XQUEUE_AUTH_PASSWORD`` (default: ``"{{ 8|random_string }}"``) +- ``XQUEUE_MYSQL_PASSWORD`` (default: ``"{{ 8|random_string }}"``) +- ``XQUEUE_SECRET_KEY`` (default: ``"{{ 24|random_string }}"``) +- ``XQUEUE_DOCKER_IMAGE`` (default: ``"overhangio/openedx-xqueue:{{ TUTOR_VERSION }}"``) +- ``XQUEUE_AUTH_USERNAME`` (default: ``"lms"``) +- ``XQUEUE_MYSQL_DATABASE`` (default: ``"xqueue"`` +- ``XQUEUE_MYSQL_USERNAME`` (default: ``"xqueue"``) + +These values can be modified with ``tutor config save --set PARAM_NAME=VALUE`` commands. diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..367a24f --- /dev/null +++ b/setup.py @@ -0,0 +1,46 @@ +import io +import os +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) + +with io.open(os.path.join(here, "README.rst"), "rt", encoding="utf8") as f: + readme = f.read() + +about = {} +with io.open( + os.path.join(here, "tutorxqueue", "__about__.py"), "rt", encoding="utf-8" +) as f: + exec(f.read(), about) + +setup( + name="tutor-xqueue", + version=about["__version__"], + url="https://docs.tutor.overhang.io/", + project_urls={ + "Documentation": "https://docs.tutor.overhang.io/", + "Code": "https://github.com/overhangio/tutor/tree/master/plugins/xqueue", + "Issue tracker": "https://github.com/overhangio/tutor/issues", + "Community": "https://discuss.overhang.io", + }, + license="AGPLv3", + author="Overhang.io", + author_email="contact@overhang.io", + description="A Tutor plugin for Xqueue (external grading system)", + long_description=readme, + packages=find_packages(exclude=["tests*"]), + include_package_data=True, + python_requires=">=3.5", + install_requires=["tutor-openedx"], + entry_points={"tutor.plugin.v0": ["xqueue = tutorxqueue.plugin"]}, + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Affero General Public License v3", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + ], +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tutorxqueue/__about__.py b/tutorxqueue/__about__.py new file mode 100644 index 0000000..d1f2e39 --- /dev/null +++ b/tutorxqueue/__about__.py @@ -0,0 +1 @@ +__version__ = "0.1.1" \ No newline at end of file diff --git a/tutorxqueue/__init__.py b/tutorxqueue/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tutorxqueue/patches/k8s-deployments b/tutorxqueue/patches/k8s-deployments new file mode 100644 index 0000000..7cc0f14 --- /dev/null +++ b/tutorxqueue/patches/k8s-deployments @@ -0,0 +1,43 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: xqueue + labels: + app.kubernetes.io/name: xqueue +spec: + selector: + matchLabels: + app.kubernetes.io/name: xqueue + template: + metadata: + labels: + app.kubernetes.io/name: xqueue + spec: + containers: + - name: xqueue + image: {{ DOCKER_REGISTRY }}{{ XQUEUE_DOCKER_IMAGE }} + ports: + - containerPort: 8040 + env: + - name: DJANGO_SETTINGS_MODULE + value: xqueue.tutor + volumeMounts: + - mountPath: /openedx/xqueue/xqueue/tutor.py + name: settings + subPath: tutor.py + - name: xqueue-consumer + image: {{ DOCKER_REGISTRY }}{{ XQUEUE_DOCKER_IMAGE }} + command: ["sh", "-e", "-c"] + args: ["while true; do echo 'running consumers'; ./manage.py run_consumer; sleep 10; done"] + env: + - name: DJANGO_SETTINGS_MODULE + value: xqueue.tutor + volumeMounts: + - mountPath: /openedx/xqueue/xqueue/tutor.py + name: settings + subPath: tutor.py + volumes: + - name: settings + configMap: + name: xqueue-settings \ No newline at end of file diff --git a/tutorxqueue/patches/k8s-services b/tutorxqueue/patches/k8s-services new file mode 100644 index 0000000..d4a227e --- /dev/null +++ b/tutorxqueue/patches/k8s-services @@ -0,0 +1,12 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: xqueue +spec: + type: NodePort + ports: + - port: 8040 + protocol: TCP + selector: + app.kubernetes.io/name: xqueue \ No newline at end of file diff --git a/tutorxqueue/patches/kustomization-configmapgenerator b/tutorxqueue/patches/kustomization-configmapgenerator new file mode 100644 index 0000000..baba3f8 --- /dev/null +++ b/tutorxqueue/patches/kustomization-configmapgenerator @@ -0,0 +1,3 @@ +- name: xqueue-settings + files: + - plugins/xqueue/apps/xqueue/settings/tutor.py \ No newline at end of file diff --git a/tutorxqueue/patches/local-docker-compose-services b/tutorxqueue/patches/local-docker-compose-services new file mode 100644 index 0000000..5424688 --- /dev/null +++ b/tutorxqueue/patches/local-docker-compose-services @@ -0,0 +1,24 @@ +############# Xqueue: external grading of Open edX problems +xqueue: + image: {{ DOCKER_REGISTRY }}{{ XQUEUE_DOCKER_IMAGE }} + volumes: + - ../plugins/xqueue/apps/settings/tutor.py:/openedx/xqueue/xqueue/tutor.py + - ../../data/xqueue:/openedx/data + environment: + DJANGO_SETTINGS_MODULE: xqueue.tutor + restart: unless-stopped + {% if ACTIVATE_MYSQL %}depends_on: + - mysql{% endif %} + +xqueue_consumer: + image: {{ DOCKER_REGISTRY }}{{ XQUEUE_DOCKER_IMAGE }} + volumes: + - ../plugins/xqueue/apps/settings/tutor.py:/openedx/xqueue/xqueue/tutor.py + - ../../data/xqueue:/openedx/data + environment: + DJANGO_SETTINGS_MODULE: xqueue.tutor + restart: unless-stopped + entrypoint: ["sh", "-e", "-c"] + command: ["while true; do echo 'running consumers'; ./manage.py run_consumer; sleep 10; done"] + {% if ACTIVATE_MYSQL %}depends_on: + - mysql{% endif %} \ No newline at end of file diff --git a/tutorxqueue/patches/openedx-common-settings b/tutorxqueue/patches/openedx-common-settings new file mode 100644 index 0000000..cdb3f10 --- /dev/null +++ b/tutorxqueue/patches/openedx-common-settings @@ -0,0 +1,7 @@ +XQUEUE_INTERFACE = { + "django_auth": { + "username": "{{ XQUEUE_AUTH_USERNAME }}", + "password": "{{ XQUEUE_AUTH_PASSWORD }}" + }, + "url": "http://xqueue:8040" +} \ No newline at end of file diff --git a/tutorxqueue/plugin.py b/tutorxqueue/plugin.py new file mode 100644 index 0000000..9b00c08 --- /dev/null +++ b/tutorxqueue/plugin.py @@ -0,0 +1,41 @@ +from glob import glob +import os + +import pkg_resources + +from .__about__ import __version__ + + +config = { + "add": { + "AUTH_PASSWORD": "{{ 8|random_string }}", + "MYSQL_PASSWORD": "{{ 8|random_string }}", + "SECRET_KEY": "{{ 24|random_string }}", + }, + "defaults": { + "VERSION": __version__, + "DOCKER_IMAGE": "overhangio/openedx-xqueue:{{ XQUEUE_VERSION }}", + "AUTH_USERNAME": "lms", + "MYSQL_DATABASE": "xqueue", + "MYSQL_USERNAME": "xqueue", + }, +} + +templates = pkg_resources.resource_filename("tutorxqueue", "templates") +hooks = { + "init": ["mysql-client", "xqueue"], + "build-image": {"xqueue": "{{ XQUEUE_DOCKER_IMAGE }}"}, + "remote-image": {"xqueue": "{{ XQUEUE_DOCKER_IMAGE }}"}, +} + + +def patches(): + all_patches = {} + for path in glob( + os.path.join(pkg_resources.resource_filename("tutorxqueue", "patches"), "*") + ): + with open(path) as patch_file: + name = os.path.basename(path) + content = patch_file.read() + all_patches[name] = content + return all_patches diff --git a/tutorxqueue/templates/xqueue/apps/settings/tutor.py b/tutorxqueue/templates/xqueue/apps/settings/tutor.py new file mode 100644 index 0000000..d6e80c0 --- /dev/null +++ b/tutorxqueue/templates/xqueue/apps/settings/tutor.py @@ -0,0 +1,18 @@ +from .settings import * + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.mysql", + "HOST": "{{ MYSQL_HOST }}", + "PORT": {{MYSQL_PORT}}, + "NAME": "{{ XQUEUE_MYSQL_DATABASE }}", + "USER": "{{ XQUEUE_MYSQL_USERNAME }}", + "PASSWORD": "{{ XQUEUE_MYSQL_PASSWORD }}", + } +} + +LOGGING = get_logger_config(log_dir="/openedx/data/", logging_env="tutor", dev_env=True) + +SECRET_KEY = "{{ XQUEUE_SECRET_KEY }}" + +XQUEUE_USERS = {"{{ XQUEUE_AUTH_USERNAME }}": "{{ XQUEUE_AUTH_PASSWORD}}"} diff --git a/tutorxqueue/templates/xqueue/build/xqueue/Dockerfile b/tutorxqueue/templates/xqueue/build/xqueue/Dockerfile new file mode 100644 index 0000000..8538162 --- /dev/null +++ b/tutorxqueue/templates/xqueue/build/xqueue/Dockerfile @@ -0,0 +1,15 @@ +FROM ubuntu:18.04 +MAINTAINER Overhang.io + +RUN apt update && \ + apt upgrade -y && \ + apt install -y language-pack-en git python-pip libmysqlclient-dev + +RUN mkdir /openedx /openedx/data +RUN git clone https://github.com/edx/xqueue --branch open-release/ironwood.2 --depth 1 /openedx/xqueue +WORKDIR /openedx/xqueue + +RUN pip install -r requirements.txt + +EXPOSE 8040 +CMD gunicorn --workers=2 --name xqueue --bind=0.0.0.0:8040 --max-requests=1000 xqueue.wsgi:application diff --git a/tutorxqueue/templates/xqueue/hooks/mysql-client/init b/tutorxqueue/templates/xqueue/hooks/mysql-client/init new file mode 100644 index 0000000..9731e4b --- /dev/null +++ b/tutorxqueue/templates/xqueue/hooks/mysql-client/init @@ -0,0 +1,2 @@ +mysql -u root --password="{{ MYSQL_ROOT_PASSWORD }}" --host "{{ MYSQL_HOST }}" --port {{ MYSQL_PORT }} -e 'CREATE DATABASE IF NOT EXISTS {{ XQUEUE_MYSQL_DATABASE }};' +mysql -u root --password="{{ MYSQL_ROOT_PASSWORD }}" --host "{{ MYSQL_HOST }}" --port {{ MYSQL_PORT }} -e 'GRANT ALL ON {{ XQUEUE_MYSQL_DATABASE }}.* TO "{{ XQUEUE_MYSQL_USERNAME }}"@"%" IDENTIFIED BY "{{ XQUEUE_MYSQL_PASSWORD }}";' diff --git a/tutorxqueue/templates/xqueue/hooks/xqueue/init b/tutorxqueue/templates/xqueue/hooks/xqueue/init new file mode 100644 index 0000000..f0c3d44 --- /dev/null +++ b/tutorxqueue/templates/xqueue/hooks/xqueue/init @@ -0,0 +1 @@ +./manage.py migrate