Skip to content

Commit

Permalink
feat!: remove most Old Mongo functionality (#31134)
Browse files Browse the repository at this point in the history
This commit leaves behind just enough Old Mongo (DraftModulestore)
functionality to allow read-only access to static assets and the
root CourseBlock. It removes:

* create/update operations
* child/parent traversal
* inheritance related code

It also removes or converts tests for this functionality.

The ability to read from the root CourseBlock was maintained for
backwards compatibility, since top-level course settings are often
stored here, and this is used by various parts of the codebase,
like displaying dashboards and re-building CourseOverview models.

Any attempt to read the contents of a course by getting the
CourseBlock's children will return an empty list (i.e. it will look
empty).

This commit does _not_ delete content on MongoDB or run any sort of
data migration or cleanup.
  • Loading branch information
UvgenGen authored Sep 6, 2023
1 parent a1d840f commit c5d1807
Show file tree
Hide file tree
Showing 40 changed files with 667 additions and 3,150 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""


from unittest import skip
from django.conf import settings
from django.core.management import call_command
from opaque_keys.edx.keys import CourseKey
Expand All @@ -20,6 +21,9 @@
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT


@skip("OldMongo Deprecation")
# This test worked only for Old Mongo
# Can later be converted to work with Split
class ExportAllCourses(ModuleStoreTestCase):
"""
Tests assets cleanup for all courses.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from io import StringIO

import ddt
from django.core.management import CommandError, call_command
from django.test import TestCase

Expand Down Expand Up @@ -40,27 +39,28 @@ def test_nonexistent_user_email(self):
call_command('create_course', "mongo", "[email protected]", "org", "course", "run")


@ddt.ddt
class TestCreateCourse(ModuleStoreTestCase):
"""
Unit tests for creating a course in either old mongo or split mongo via command line
"""

@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_all_stores_user_email(self, store):
def test_all_stores_user_email(self):
call_command(
"create_course",
store,
ModuleStoreEnum.Type.split,
str(self.user.email),
"org", "course", "run", "dummy-course-name"
)
new_key = modulestore().make_course_key("org", "course", "run")
self.assertTrue(
modulestore().has_course(new_key),
f"Could not find course in {store}"
f"Could not find course in {ModuleStoreEnum.Type.split}"
)
# pylint: disable=protected-access
self.assertEqual(store, modulestore()._get_modulestore_for_courselike(new_key).get_modulestore_type())
self.assertEqual(
ModuleStoreEnum.Type.split,
modulestore()._get_modulestore_for_courselike(new_key).get_modulestore_type()
)

def test_duplicate_course(self):
"""
Expand All @@ -85,8 +85,7 @@ def test_duplicate_course(self):
expected = "Course already exists"
self.assertIn(out.getvalue().strip(), expected)

@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
def test_get_course_with_different_case(self, default_store):
def test_get_course_with_different_case(self):
"""
Tests that course can not be accessed with different case.
Expand All @@ -98,21 +97,20 @@ def test_get_course_with_different_case(self, default_store):
org = 'org1'
number = 'course1'
run = 'run1'
with self.store.default_store(default_store):
lowercase_course_id = self.store.make_course_key(org, number, run)
with self.store.bulk_operations(lowercase_course_id, ignore_case=True):
# Create course with lowercase key & Verify that store returns course.
self.store.create_course(
lowercase_course_id.org,
lowercase_course_id.course,
lowercase_course_id.run,
self.user.id
)
course = self.store.get_course(lowercase_course_id)
self.assertIsNotNone(course, 'Course not found using lowercase course key.')
self.assertEqual(str(course.id), str(lowercase_course_id))

# Verify store does not return course with different case.
uppercase_course_id = self.store.make_course_key(org.upper(), number.upper(), run.upper())
course = self.store.get_course(uppercase_course_id)
self.assertIsNone(course, 'Course should not be accessed with uppercase course id.')
lowercase_course_id = self.store.make_course_key(org, number, run)
with self.store.bulk_operations(lowercase_course_id, ignore_case=True):
# Create course with lowercase key & Verify that store returns course.
self.store.create_course(
lowercase_course_id.org,
lowercase_course_id.course,
lowercase_course_id.run,
self.user.id
)
course = self.store.get_course(lowercase_course_id)
self.assertIsNotNone(course, 'Course not found using lowercase course key.')
self.assertEqual(str(course.id), str(lowercase_course_id))

# Verify store does not return course with different case.
uppercase_course_id = self.store.make_course_key(org.upper(), number.upper(), run.upper())
course = self.store.get_course(uppercase_course_id)
self.assertIsNone(course, 'Course should not be accessed with uppercase course id.')
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import shutil
from tempfile import mkdtemp
from unittest import skip

from cms.djangoapps.contentstore.management.commands.export_all_courses import export_courses_to_output_path
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
Expand All @@ -13,6 +14,10 @@
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order


@skip("OldMongo Deprecation")
# This test fails for split modulestre
# AttributeError: 'MixedModuleStore' object has no attribute 'collection'
# split module store has no 'collection' attribute.
class ExportAllCourses(ModuleStoreTestCase):
"""
Tests exporting all courses.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,6 @@ def test_no_args(self):
with self.assertRaisesRegex(CommandError, msg):
call_command('fix_not_found')

def test_fix_not_found_non_split(self):
"""
The management command doesn't work on non split courses
"""
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.mongo)
with self.assertRaisesRegex(CommandError, "The owning modulestore does not support this command."):
call_command("fix_not_found", str(course.id))

def test_fix_not_found(self):
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
BlockFactory.create(category='chapter', parent_location=course.location)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,6 @@ def test_course_key_not_found(self):
with self.assertRaisesRegex(CommandError, errstring):
call_command('force_publish', 'course-v1:org+course+run')

def test_force_publish_non_split(self):
"""
Test 'force_publish' command doesn't work on non split courses
"""
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.mongo)
errstring = 'The owning modulestore does not support this command.'
with self.assertRaisesRegex(CommandError, errstring):
call_command('force_publish', str(course.id))


class TestForcePublishModifications(ModuleStoreTestCase):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from path import Path as path

from openedx.core.djangoapps.django_comment_common.utils import are_permissions_roles_seeded
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order

Expand Down Expand Up @@ -73,23 +72,3 @@ def test_truncated_course_with_url(self):
# Now load up the course with a similar course_id and verify it loads
call_command('import', self.content_dir, self.course_dir)
self.assertIsNotNone(store.get_course(self.truncated_key))

def test_existing_course_with_different_modulestore(self):
"""
Checks that a course that originally existed in old mongo can be re-imported when
split is the default modulestore.
"""
with modulestore().default_store(ModuleStoreEnum.Type.mongo):
call_command('import', self.content_dir, self.good_dir)

# Clear out the modulestore mappings, else when the next import command goes to create a destination
# course_key, it will find the existing course and return the mongo course_key. To reproduce TNL-1362,
# the destination course_key needs to be the one for split modulestore.
modulestore().mappings = {}

with modulestore().default_store(ModuleStoreEnum.Type.split):
call_command('import', self.content_dir, self.good_dir)
course = modulestore().get_course(self.base_course_key)
# With the bug, this fails because the chapter's course_key is the split mongo form,
# while the course's course_key is the old mongo form.
self.assertEqual(str(course.location.course_key), str(course.children[0].course_key))
25 changes: 2 additions & 23 deletions cms/djangoapps/contentstore/tests/test_import_pure_xblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
from xblock.core import XBlock
from xblock.fields import String

from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.mongo.draft import as_draft
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.xml_importer import import_course_from_xml

Expand Down Expand Up @@ -41,14 +38,6 @@ def test_import_public(self):
'set by xml'
)

@XBlock.register_temp_plugin(StubXBlock)
def test_import_draft(self):
self._assert_import(
'pure_xblock_draft',
'set by xml',
has_draft=True
)

def _assert_import(self, course_dir, expected_field_val, has_draft=False):
"""
Import a course from XML, then verify that the XBlock was loaded
Expand All @@ -66,22 +55,12 @@ def _assert_import(self, course_dir, expected_field_val, has_draft=False):
"""
# It is necessary to use the "old mongo" modulestore because split doesn't work
# with the "has_draft" logic below.
store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo) # pylint: disable=protected-access
courses = import_course_from_xml(
store, self.user.id, TEST_DATA_DIR, [course_dir], create_if_not_present=True
self.store, self.user.id, TEST_DATA_DIR, [course_dir], create_if_not_present=True
)

xblock_location = courses[0].id.make_usage_key('stubxblock', 'xblock_test')

if has_draft:
xblock_location = as_draft(xblock_location)

xblock = store.get_item(xblock_location)
xblock = self.store.get_item(xblock_location)
self.assertTrue(isinstance(xblock, StubXBlock))
self.assertEqual(xblock.test_field, expected_field_val)

if has_draft:
draft_xblock = store.get_item(xblock_location)
self.assertTrue(getattr(draft_xblock, 'is_draft', False))
self.assertTrue(isinstance(draft_xblock, StubXBlock))
self.assertEqual(draft_xblock.test_field, expected_field_val)
25 changes: 0 additions & 25 deletions cms/djangoapps/contentstore/tests/test_libraries.py
Original file line number Diff line number Diff line change
Expand Up @@ -1014,28 +1014,3 @@ def test_duplicated_version(self):
self.assertEqual(self.lc_block.source_library_version, duplicate.source_library_version)
problem2_in_course = store.get_item(duplicate.children[0])
self.assertEqual(problem2_in_course.display_name, self.original_display_name)


class TestIncompatibleModuleStore(LibraryTestCase):
"""
Tests for proper validation errors with an incompatible course modulestore.
"""

def setUp(self):
super().setUp()
# Create a course in an incompatible modulestore.
with modulestore().default_store(ModuleStoreEnum.Type.mongo):
self.course = CourseFactory.create()

# Add a LibraryContent block to the course:
self.lc_block = self._add_library_content_block(self.course, self.lib_key)

def test_incompatible_modulestore(self):
"""
Verifies that, if a user is using a modulestore that doesn't support libraries,
a validation error will be produced.
"""
validation = self.lc_block.validate()
self.assertEqual(validation.summary.type, validation.summary.ERROR)
self.assertIn(
"This course does not support content libraries.", validation.summary.text)
6 changes: 3 additions & 3 deletions cms/djangoapps/contentstore/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from xmodule.contentstore.django import contentstore
from xmodule.modulestore.inheritance import own_metadata
from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
from xmodule.modulestore.tests.django_utils import TEST_DATA_MONGO_MODULESTORE, ModuleStoreTestCase
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.utils import ProceduralCourseTestMixin
from xmodule.tests.test_transcripts_utils import YoutubeVideoHTMLResponse
Expand Down Expand Up @@ -73,7 +73,7 @@ class CourseTestCase(ProceduralCourseTestMixin, ModuleStoreTestCase):
Base class for Studio tests that require a logged in user and a course.
Also provides helper methods for manipulating and verifying the course.
"""
MODULESTORE = TEST_DATA_MONGO_MODULESTORE
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE

def setUp(self):
"""
Expand Down Expand Up @@ -123,7 +123,7 @@ def save_course(self):
SEQUENTIAL = 'vertical_sequential'
DRAFT_HTML = 'draft_html'
DRAFT_VIDEO = 'draft_video'
LOCKED_ASSET_KEY = AssetKey.from_string('/c4x/edX/toy/asset/sample_static.html')
LOCKED_ASSET_KEY = AssetKey.from_string('asset-v1:edX+toy+2012_Fall+type@asset+block@sample_static.html')

def assertCoursesEqual(self, course1_id, course2_id):
"""
Expand Down
Loading

0 comments on commit c5d1807

Please sign in to comment.