Commit d3b873c7 by Jeremy Bowman

PLAT-1104 Import courses asynchronously

parent fc8e7fe2
...@@ -75,6 +75,10 @@ jscover.log.* ...@@ -75,6 +75,10 @@ jscover.log.*
.tddium* .tddium*
common/test/data/test_unicode/static/ common/test/data/test_unicode/static/
test_root/courses/ test_root/courses/
test_root/data/test_bare.git/
test_root/export_course_repos/
test_root/paver_logs/
test_root/uploads/
django-pyfs django-pyfs
### Installation artifacts ### Installation artifacts
......
"""
Storage backend for course import and export.
"""
from __future__ import absolute_import
from django.conf import settings
from django.core.files.storage import get_storage_class
from storages.backends.s3boto import S3BotoStorage
from storages.utils import setting
class ImportExportS3Storage(S3BotoStorage): # pylint: disable=abstract-method
"""
S3 backend for course import and export OLX files.
"""
def __init__(self):
bucket = setting('COURSE_IMPORT_EXPORT_BUCKET', settings.AWS_STORAGE_BUCKET_NAME)
super(ImportExportS3Storage, self).__init__(bucket=bucket, querystring_auth=True)
# pylint: disable=invalid-name
course_import_export_storage = get_storage_class(settings.COURSE_IMPORT_EXPORT_STORAGE)()
...@@ -184,7 +184,7 @@ class ImportTestCase(CourseTestCase): ...@@ -184,7 +184,7 @@ class ImportTestCase(CourseTestCase):
"name": self.bad_tar, "name": self.bad_tar,
"course-data": [btar] "course-data": [btar]
}) })
self.assertEquals(resp.status_code, 415) self.assertEquals(resp.status_code, 200)
# Check that `import_status` returns the appropriate stage (i.e., the # Check that `import_status` returns the appropriate stage (i.e., the
# stage at which import failed). # stage at which import failed).
resp_status = self.client.get( resp_status = self.client.get(
...@@ -336,8 +336,16 @@ class ImportTestCase(CourseTestCase): ...@@ -336,8 +336,16 @@ class ImportTestCase(CourseTestCase):
with open(tarpath) as tar: with open(tarpath) as tar:
args = {"name": tarpath, "course-data": [tar]} args = {"name": tarpath, "course-data": [tar]}
resp = self.client.post(self.url, args) resp = self.client.post(self.url, args)
self.assertEquals(resp.status_code, 400) self.assertEquals(resp.status_code, 200)
self.assertIn("SuspiciousFileOperation", resp.content) resp = self.client.get(
reverse_course_url(
'import_status_handler',
self.course.id,
kwargs={'filename': os.path.split(tarpath)[1]}
)
)
status = json.loads(resp.content)["ImportStatus"]
self.assertEqual(status, -1)
try_tar(self._fifo_tar()) try_tar(self._fifo_tar())
try_tar(self._symlink_tar()) try_tar(self._symlink_tar())
......
...@@ -299,10 +299,17 @@ AWS_SECRET_ACCESS_KEY = AUTH_TOKENS["AWS_SECRET_ACCESS_KEY"] ...@@ -299,10 +299,17 @@ AWS_SECRET_ACCESS_KEY = AUTH_TOKENS["AWS_SECRET_ACCESS_KEY"]
if AWS_SECRET_ACCESS_KEY == "": if AWS_SECRET_ACCESS_KEY == "":
AWS_SECRET_ACCESS_KEY = None AWS_SECRET_ACCESS_KEY = None
AWS_STORAGE_BUCKET_NAME = AUTH_TOKENS.get('AWS_STORAGE_BUCKET_NAME', 'edxuploads')
# Disabling querystring auth instructs Boto to exclude the querystring parameters (e.g. signature, access key) it # Disabling querystring auth instructs Boto to exclude the querystring parameters (e.g. signature, access key) it
# normally appends to every returned URL. # normally appends to every returned URL.
AWS_QUERYSTRING_AUTH = AUTH_TOKENS.get('AWS_QUERYSTRING_AUTH', True) AWS_QUERYSTRING_AUTH = AUTH_TOKENS.get('AWS_QUERYSTRING_AUTH', True)
AWS_DEFAULT_ACL = 'private'
AWS_BUCKET_ACL = AWS_DEFAULT_ACL
AWS_QUERYSTRING_EXPIRE = 7 * 24 * 60 * 60 # 7 days
AWS_S3_CUSTOM_DOMAIN = AUTH_TOKENS.get('AWS_S3_CUSTOM_DOMAIN', 'edxuploads.s3.amazonaws.com')
if AUTH_TOKENS.get('DEFAULT_FILE_STORAGE'): if AUTH_TOKENS.get('DEFAULT_FILE_STORAGE'):
DEFAULT_FILE_STORAGE = AUTH_TOKENS.get('DEFAULT_FILE_STORAGE') DEFAULT_FILE_STORAGE = AUTH_TOKENS.get('DEFAULT_FILE_STORAGE')
elif AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY: elif AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
...@@ -310,6 +317,15 @@ elif AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY: ...@@ -310,6 +317,15 @@ elif AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
else: else:
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
COURSE_IMPORT_EXPORT_BUCKET = ENV_TOKENS.get('COURSE_IMPORT_EXPORT_BUCKET', '')
if COURSE_IMPORT_EXPORT_BUCKET:
COURSE_IMPORT_EXPORT_STORAGE = 'contentstore.storage.ImportExportS3Storage'
else:
COURSE_IMPORT_EXPORT_STORAGE = DEFAULT_FILE_STORAGE
USER_TASKS_ARTIFACT_STORAGE = COURSE_IMPORT_EXPORT_STORAGE
DATABASES = AUTH_TOKENS['DATABASES'] DATABASES = AUTH_TOKENS['DATABASES']
# The normal database user does not have enough permissions to run migrations. # The normal database user does not have enough permissions to run migrations.
......
...@@ -555,6 +555,8 @@ LOCALE_PATHS = (REPO_ROOT + '/conf/locale',) # edx-platform/conf/locale/ ...@@ -555,6 +555,8 @@ LOCALE_PATHS = (REPO_ROOT + '/conf/locale',) # edx-platform/conf/locale/
# Messages # Messages
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
COURSE_IMPORT_EXPORT_STORAGE = 'django.core.files.storage.FileSystemStorage'
##### EMBARGO ##### ##### EMBARGO #####
EMBARGO_SITE_REDIRECT_URL = None EMBARGO_SITE_REDIRECT_URL = None
......
...@@ -8,6 +8,8 @@ from .aws import * # pylint: disable=wildcard-import, unused-wildcard-import ...@@ -8,6 +8,8 @@ from .aws import * # pylint: disable=wildcard-import, unused-wildcard-import
# Don't use S3 in devstack, fall back to filesystem # Don't use S3 in devstack, fall back to filesystem
del DEFAULT_FILE_STORAGE del DEFAULT_FILE_STORAGE
COURSE_IMPORT_EXPORT_STORAGE = 'django.core.files.storage.FileSystemStorage'
USER_TASKS_ARTIFACT_STORAGE = COURSE_IMPORT_EXPORT_STORAGE
MEDIA_ROOT = "/edx/var/edxapp/uploads" MEDIA_ROOT = "/edx/var/edxapp/uploads"
DEBUG = True DEBUG = True
......
...@@ -81,7 +81,7 @@ define( ...@@ -81,7 +81,7 @@ define(
*/ */
var initEventListeners = function() { var initEventListeners = function() {
$(window).on('beforeunload.import', function() { $(window).on('beforeunload.import', function() {
if (current.stage <= STAGE.UNPACKING) { if (current.stage < STAGE.UNPACKING) {
return gettext('Your import is in progress; navigating away will abort it.'); return gettext('Your import is in progress; navigating away will abort it.');
} }
}); });
......
...@@ -118,7 +118,7 @@ else: ...@@ -118,7 +118,7 @@ else:
<li class="item-progresspoint item-progresspoint-unpack is-started"> <li class="item-progresspoint item-progresspoint-unpack is-started">
<span class="deco status-visual"> <span class="deco status-visual">
<span class="icon fa fa-cog" aria-hidden="true"></span> <span class="icon fa fa-cog" aria-hidden="true"></span>
<span class="icon fa fa-warning" aria-hidden="true"v></span> <span class="icon fa fa-warning" aria-hidden="true"></span>
</span> </span>
<div class="status-detail"> <div class="status-detail">
......
...@@ -2186,6 +2186,9 @@ CSRF_COOKIE_SECURE = False ...@@ -2186,6 +2186,9 @@ CSRF_COOKIE_SECURE = False
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'openedx.core.lib.api.paginators.DefaultPagination', 'DEFAULT_PAGINATION_CLASS': 'openedx.core.lib.api.paginators.DefaultPagination',
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
),
'PAGE_SIZE': 10, 'PAGE_SIZE': 10,
'URL_FORMAT_OVERRIDE': None, 'URL_FORMAT_OVERRIDE': None,
'DEFAULT_THROTTLE_RATES': { 'DEFAULT_THROTTLE_RATES': {
......
...@@ -141,7 +141,7 @@ class TestPaverServerTasks(PaverTestCase): ...@@ -141,7 +141,7 @@ class TestPaverServerTasks(PaverTestCase):
""" """
Test the "celery" task. Test the "celery" task.
""" """
settings = options.get("settings", "dev_with_worker") settings = options.get("settings", "devstack_with_worker")
call_task("pavelib.servers.celery", options=options) call_task("pavelib.servers.celery", options=options)
self.assertEquals(self.task_messages, [EXPECTED_CELERY_COMMAND.format(settings=settings)]) self.assertEquals(self.task_messages, [EXPECTED_CELERY_COMMAND.format(settings=settings)])
...@@ -292,7 +292,7 @@ class TestPaverServerTasks(PaverTestCase): ...@@ -292,7 +292,7 @@ class TestPaverServerTasks(PaverTestCase):
port=8001, port=8001,
) )
) )
expected_messages.append(EXPECTED_CELERY_COMMAND.format(settings="dev_with_worker")) expected_messages.append(EXPECTED_CELERY_COMMAND.format(settings="devstack_with_worker"))
self.assertEquals(self.task_messages, expected_messages) self.assertEquals(self.task_messages, expected_messages)
def expected_sass_commands(self, system=None, asset_settings=u"test_static_optimized"): def expected_sass_commands(self, system=None, asset_settings=u"test_static_optimized"):
......
...@@ -157,7 +157,7 @@ def celery(options): ...@@ -157,7 +157,7 @@ def celery(options):
""" """
Runs Celery workers. Runs Celery workers.
""" """
settings = getattr(options, 'settings', 'dev_with_worker') settings = getattr(options, 'settings', 'devstack_with_worker')
run_process(django_cmd('lms', settings, 'celery', 'worker', '--beat', '--loglevel=INFO', '--pythonpath=.')) run_process(django_cmd('lms', settings, 'celery', 'worker', '--beat', '--loglevel=INFO', '--pythonpath=.'))
...@@ -187,7 +187,7 @@ def run_all_servers(options): ...@@ -187,7 +187,7 @@ def run_all_servers(options):
""" """
settings = getattr(options, 'settings', DEFAULT_SETTINGS) settings = getattr(options, 'settings', DEFAULT_SETTINGS)
asset_settings = getattr(options, 'asset_settings', settings) asset_settings = getattr(options, 'asset_settings', settings)
worker_settings = getattr(options, 'worker_settings', 'dev_with_worker') worker_settings = getattr(options, 'worker_settings', 'devstack_with_worker')
fast = getattr(options, 'fast', False) fast = getattr(options, 'fast', False)
optimized = getattr(options, 'optimized', False) optimized = getattr(options, 'optimized', False)
......
...@@ -34,7 +34,7 @@ django-simple-history==1.6.3 ...@@ -34,7 +34,7 @@ django-simple-history==1.6.3
django-statici18n==1.1.5 django-statici18n==1.1.5
django-storages==1.4.1 django-storages==1.4.1
django-method-override==0.1.0 django-method-override==0.1.0
django-user-tasks==0.1.2 django-user-tasks==0.1.4
# We need a fix to DRF 3.2.x, for now use it from our own cherry-picked repo # We need a fix to DRF 3.2.x, for now use it from our own cherry-picked repo
#djangorestframework>=3.1,<3.2 #djangorestframework>=3.1,<3.2
git+https://github.com/edx/django-rest-framework.git@3c72cb5ee5baebc4328947371195eae2077197b0#egg=djangorestframework==3.2.3 git+https://github.com/edx/django-rest-framework.git@3c72cb5ee5baebc4328947371195eae2077197b0#egg=djangorestframework==3.2.3
......
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