Revert "feat: store split modulestore's course indexes in Django/MySQL"

This reverts commit 96e5ff8dce.
This commit is contained in:
David Ormsbee
2021-10-07 13:22:57 -04:00
parent da2bae21ae
commit ae124bd554
52 changed files with 205 additions and 729 deletions

View File

@@ -1,18 +0,0 @@
"""
Admin registration for Split Modulestore Django Backend
"""
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import SplitModulestoreCourseIndex
@admin.register(SplitModulestoreCourseIndex)
class SplitModulestoreCourseIndexAdmin(SimpleHistoryAdmin):
"""
Admin config for course indexes
"""
list_display = ('course_id', 'draft_version', 'published_version', 'library_version', 'wiki_slug', 'last_update')
search_fields = ('course_id', 'wiki_slug')
ordering = ('course_id', )
readonly_fields = ('id', 'objectid', 'course_id', 'org', )

View File

@@ -1,12 +0,0 @@
"""
Define this module as a Django app
"""
from django.apps import AppConfig
class SplitModulestoreDjangoBackendAppConfig(AppConfig):
"""
Django app that provides a backend for Split Modulestore instead of MongoDB.
"""
name = 'common.djangoapps.split_modulestore_django'
verbose_name = "Split Modulestore Django Backend"

View File

@@ -1,65 +0,0 @@
# Generated by Django 2.2.20 on 2021-05-07 18:29
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import opaque_keys.edx.django.models
import simple_history.models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='SplitModulestoreCourseIndex',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('objectid', models.CharField(max_length=24, unique=True)),
('course_id', opaque_keys.edx.django.models.LearningContextKeyField(db_index=True, max_length=255, unique=True)),
('org', models.CharField(db_index=True, max_length=255)),
('draft_version', models.CharField(blank=True, max_length=24)),
('published_version', models.CharField(blank=True, max_length=24)),
('library_version', models.CharField(blank=True, max_length=24)),
('wiki_slug', models.CharField(db_index=True, blank=True, max_length=255)),
('base_store', models.CharField(choices=[('mongodb', 'MongoDB'), ('django', 'Django - not implemented yet')], max_length=20)),
('edited_on', models.DateTimeField()),
('last_update', models.DateTimeField()),
('edited_by_id', models.IntegerField(null=True)),
],
options={'ordering': ['course_id'], 'verbose_name_plural': 'Split modulestore course indexes'},
),
migrations.CreateModel(
name='HistoricalSplitModulestoreCourseIndex',
fields=[
('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('objectid', models.CharField(db_index=True, max_length=24)),
('course_id', opaque_keys.edx.django.models.LearningContextKeyField(db_index=True, max_length=255)),
('org', models.CharField(db_index=True, max_length=255)),
('draft_version', models.CharField(blank=True, max_length=24)),
('published_version', models.CharField(blank=True, max_length=24)),
('library_version', models.CharField(blank=True, max_length=24)),
('wiki_slug', models.CharField(db_index=True, blank=True, max_length=255)),
('base_store', models.CharField(choices=[('mongodb', 'MongoDB'), ('django', 'Django - not implemented yet')], max_length=20)),
('edited_on', models.DateTimeField()),
('last_update', models.DateTimeField()),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField()),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('edited_by_id', models.IntegerField(null=True)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical split modulestore course index',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': 'history_date',
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]

View File

@@ -1,41 +0,0 @@
from django.db import migrations, models
from django.db.utils import IntegrityError
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from ..models import SplitModulestoreCourseIndex as SplitModulestoreCourseIndex_Real
def forwards_func(apps, schema_editor):
"""
Copy all course index data from MongoDB to MySQL.
"""
db_alias = schema_editor.connection.alias
SplitModulestoreCourseIndex = apps.get_model("split_modulestore_django", "SplitModulestoreCourseIndex")
split_modulestore = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.split)
for course_index in split_modulestore.db_connection.find_matching_course_indexes(force_mongo=True):
data = SplitModulestoreCourseIndex_Real.fields_from_v1_schema(course_index)
SplitModulestoreCourseIndex(**data).save(using=db_alias)
def reverse_func(apps, schema_editor):
"""
Reverse the data migration, deleting all entries in this table.
"""
db_alias = schema_editor.connection.alias
SplitModulestoreCourseIndex = apps.get_model("split_modulestore_django", "SplitModulestoreCourseIndex")
SplitModulestoreCourseIndex.objects.using(db_alias).all().delete()
class Migration(migrations.Migration):
dependencies = [
('split_modulestore_django', '0001_initial'),
]
operations = [
migrations.RunPython(forwards_func, reverse_func),
]

View File

@@ -1,164 +0,0 @@
"""
Django model to store the "course index" data
"""
from bson.objectid import ObjectId
from django.contrib.auth import get_user_model
from django.db import models
from opaque_keys.edx.locator import CourseLocator, LibraryLocator
from opaque_keys.edx.django.models import LearningContextKeyField
from simple_history.models import HistoricalRecords
from xmodule.modulestore import ModuleStoreEnum
User = get_user_model()
class SplitModulestoreCourseIndex(models.Model):
"""
A "course index" for a course in "split modulestore."
This model/table mostly stores the current version of each course.
(Well, twice for each course - "draft" and "published" branch versions are
tracked separately.)
This MySQL table / django model is designed to replace the "active_versions"
MongoDB collection. They contain the same information.
It also stores the "wiki_slug" to facilitate looking up a course
by it's wiki slug, which is required due to the nuances of the
django-wiki integration.
.. no_pii:
"""
# For compatibility with MongoDB, each course index must have an ObjectId. We still have an integer primary key too.
objectid = models.CharField(max_length=24, null=False, blank=False, unique=True)
# The ID of this course (or library). Must start with "course-v1:" or "library-v1:"
course_id = LearningContextKeyField(max_length=255, db_index=True, unique=True, null=False)
# Extract the "org" value from the course_id key so that we can search by org.
# This gets set automatically by clean()
org = models.CharField(max_length=255, db_index=True)
# Version fields: The ObjectId of the current entry in the "structures" collection, for this course.
# The version is stored separately for each "branch".
# Note that there are only three branch names allowed. Draft/published are used for courses, while "library" is used
# for content libraries.
# ModuleStoreEnum.BranchName.draft = 'draft-branch'
draft_version = models.CharField(max_length=24, null=False, blank=True)
# ModuleStoreEnum.BranchName.published = 'published-branch'
published_version = models.CharField(max_length=24, null=False, blank=True)
# ModuleStoreEnum.BranchName.library = 'library'
library_version = models.CharField(max_length=24, null=False, blank=True)
# Wiki slug for this course
wiki_slug = models.CharField(max_length=255, db_index=True, blank=True)
# Base store - whether the "structures" and "definitions" data are in MongoDB or object storage (S3)
BASE_STORE_MONGO = "mongodb"
BASE_STORE_DJANGO = "django"
BASE_STORE_CHOICES = [
(BASE_STORE_MONGO, "MongoDB"), # For now, MongoDB is the only implemented option
(BASE_STORE_DJANGO, "Django - not implemented yet"),
]
base_store = models.CharField(max_length=20, blank=False, choices=BASE_STORE_CHOICES)
# Edit history:
# ID of the user that made the latest edit. This is not a ForeignKey because some values (like
# ModuleStoreEnum.UserID.*) are not real user IDs.
edited_by_id = models.IntegerField(null=True)
edited_on = models.DateTimeField()
# last_update is different from edited_on, and is used only to prevent collisions?
last_update = models.DateTimeField()
# Keep track of the history of this table:
history = HistoricalRecords()
def __str__(self):
return f"Course Index ({self.course_id})"
class Meta:
ordering = ["course_id"]
verbose_name_plural = "Split modulestore course indexes"
def as_v1_schema(self):
""" Return in the same format as was stored in MongoDB """
versions = {}
for branch in ("draft", "published", "library"):
# The current version of this branch, a hex-encoded ObjectID - or an empty string:
version_str = getattr(self, f"{branch}_version")
if version_str:
versions[getattr(ModuleStoreEnum.BranchName, branch)] = ObjectId(version_str)
return {
"_id": ObjectId(self.objectid),
"org": self.course_id.org,
"course": self.course_id.course,
"run": self.course_id.run, # pylint: disable=no-member
"edited_by": self.edited_by_id,
"edited_on": self.edited_on,
"last_update": self.last_update,
"versions": versions,
"schema_version": 1, # This matches schema version 1, see SplitMongoModuleStore.SCHEMA_VERSION
"search_targets": {"wiki_slug": self.wiki_slug},
}
@staticmethod
def fields_from_v1_schema(values):
""" Convert the MongoDB-style dict shape to a dict of fields that match this model """
if values["run"] == LibraryLocator.RUN and ModuleStoreEnum.BranchName.library in values["versions"]:
# This is a content library:
locator = LibraryLocator(org=values["org"], library=values["course"])
else:
# This is a course:
locator = CourseLocator(org=values["org"], course=values["course"], run=values["run"])
result = {
"course_id": locator,
"org": values["org"],
"edited_by_id": values["edited_by"],
"edited_on": values["edited_on"],
"base_store": SplitModulestoreCourseIndex.BASE_STORE_MONGO,
}
if "_id" in values:
result["objectid"] = str(values["_id"]) # Convert ObjectId to its hex representation
if "last_update" in values:
result["last_update"] = values["last_update"]
if "search_targets" in values and "wiki_slug" in values["search_targets"]:
result["wiki_slug"] = values["search_targets"]["wiki_slug"]
for branch in ("draft", "published", "library"):
version = values["versions"].get(getattr(ModuleStoreEnum.BranchName, branch))
if version:
result[f"{branch}_version"] = str(version) # Convert version from ObjectId to hex string
return result
@staticmethod
def field_name_for_branch(branch_name):
""" Given a full branch name, get the name of the field in this table that stores that branch's version """
if branch_name == ModuleStoreEnum.BranchName.draft:
return "draft_version"
if branch_name == ModuleStoreEnum.BranchName.published:
return "published_version"
if branch_name == ModuleStoreEnum.BranchName.library:
return "library_version"
raise ValueError(f"Unknown branch name: {branch_name}")
def clean(self):
"""
Validation for this model
"""
super().clean()
# Check that course_id is a supported type:
course_id_str = str(self.course_id)
if not course_id_str.startswith("course-v1:") and not course_id_str.startswith("library-v1:"):
raise ValueError(
f"Split modulestore cannot store course[like] object with key {course_id_str}"
" - only course-v1/library-v1 prefixed keys are supported."
)
# Set the "org" field automatically - ensure it always matches the "org" in the course_id
self.org = self.course_id.org
def save(self, *args, **kwargs):
""" Save this model """
# Override to ensure that full_clean()/clean() is always called, so that the checks in clean() above are run.
# But don't validate_unique(), it just runs extra queries and the database enforces it anyways.
self.full_clean(validate_unique=False)
return super().save(*args, **kwargs)

View File

@@ -13,14 +13,11 @@ from contextlib import contextmanager
from time import time
from django.core.cache import caches, InvalidCacheBackendError
from django.db.transaction import TransactionManagementError
import pymongo
import pytz
from mongodb_proxy import autoretry_read
# Import this just to export it
from pymongo.errors import DuplicateKeyError # pylint: disable=unused-import
from common.djangoapps.split_modulestore_django.models import SplitModulestoreCourseIndex
from xmodule.exceptions import HeartbeatFailure
from xmodule.modulestore import BlockData
from xmodule.modulestore.split_mongo import BlockKey
@@ -246,7 +243,7 @@ class CourseStructureCache:
self.cache.set(key, compressed_pickled_data, None)
class MongoPersistenceBackend:
class MongoConnection:
"""
Segregation of pymongo functions from the data modeling mechanisms for split modulestore.
"""
@@ -432,18 +429,15 @@ class MongoPersistenceBackend:
return courses_queries
def insert_course_index(self, course_index, course_context=None, last_update_already_set=False):
def insert_course_index(self, course_index, course_context=None):
"""
Create the course_index in the db
"""
with TIMER.timer("insert_course_index", course_context):
# Set last_update which is used to avoid collisions, unless a subclass already set it before calling super()
if not last_update_already_set:
course_index['last_update'] = datetime.datetime.now(pytz.utc)
# Insert the new index:
course_index['last_update'] = datetime.datetime.now(pytz.utc)
self.course_index.insert_one(course_index)
def update_course_index(self, course_index, from_index=None, course_context=None, last_update_already_set=False):
def update_course_index(self, course_index, from_index=None, course_context=None):
"""
Update the db record for course_index.
@@ -463,10 +457,7 @@ class MongoPersistenceBackend:
'course': course_index['course'],
'run': course_index['run'],
}
# Set last_update which is used to avoid collisions, unless a subclass already set it before calling super()
if not last_update_already_set:
course_index['last_update'] = datetime.datetime.now(pytz.utc)
# Update the course index:
course_index['last_update'] = datetime.datetime.now(pytz.utc)
self.course_index.replace_one(query, course_index, upsert=False,)
def delete_course_index(self, course_key):
@@ -560,167 +551,3 @@ class MongoPersistenceBackend:
if connections:
connection.close()
class DjangoFlexPersistenceBackend(MongoPersistenceBackend):
"""
Backend for split mongo that can read/write from MySQL and/or S3 instead of Mongo,
either partially replacing MongoDB or fully replacing it.
"""
# Structures and definitions are only supported in MongoDB for now.
# Course indexes are read from MySQL and written to both MongoDB and MySQL
def get_course_index(self, key, ignore_case=False):
"""
Get the course_index from the persistence mechanism whose id is the given key
"""
if key.version_guid and not key.org:
# I don't think it was intentional, but with the MongoPersistenceBackend, using a key with only a version
# guid and no org/course/run value would not raise an error, but would always return None. So we need to be
# compatible with that.
# e.g. test_split_modulestore.py:SplitModuleCourseTests.test_get_course -> get_course(key with only version)
# > _load_items > cache_items > begin bulk operations > get_course_index > results in this situation.
log.warning("DjangoFlexPersistenceBackend: get_course_index without org/course/run will always return None")
return None
# We never include the branch or the version in the course key in the SplitModulestoreCourseIndex table:
key = key.for_branch(None).version_agnostic()
if not ignore_case:
query = {"course_id": key}
else:
# Case insensitive search is important when creating courses to reject course IDs that differ only by
# capitalization.
query = {"course_id__iexact": key}
try:
return SplitModulestoreCourseIndex.objects.get(**query).as_v1_schema()
except SplitModulestoreCourseIndex.DoesNotExist:
return None
def find_matching_course_indexes( # pylint: disable=arguments-differ
self,
branch=None,
search_targets=None,
org_target=None,
course_context=None,
course_keys=None,
force_mongo=False,
):
"""
Find the course_index matching particular conditions.
Arguments:
branch: If specified, this branch must exist in the returned courses
search_targets: If specified, this must be a dictionary specifying field values
that must exist in the search_targets of the returned courses
org_target: If specified, this is an ORG filter so that only course_indexs are
returned for the specified ORG
"""
if force_mongo:
# For data migration purposes, this argument will read from MongoDB instead of MySQL
return super().find_matching_course_indexes(
branch=branch, search_targets=search_targets, org_target=org_target,
course_context=course_context, course_keys=course_keys,
)
queryset = SplitModulestoreCourseIndex.objects.all()
if course_keys:
queryset = queryset.filter(course_id__in=course_keys)
if search_targets:
if "wiki_slug" in search_targets:
queryset = queryset.filter(wiki_slug=search_targets.pop("wiki_slug"))
if search_targets: # If there are any search targets besides wiki_slug (which we've handled by this point):
raise ValueError(f"Unsupported search_targets: {', '.join(search_targets.keys())}")
if org_target:
queryset = queryset.filter(org=org_target)
if branch is not None:
branch_field = SplitModulestoreCourseIndex.field_name_for_branch(branch)
queryset = queryset.exclude(**{branch_field: ""})
return (course_index.as_v1_schema() for course_index in queryset)
def insert_course_index(self, course_index, course_context=None): # pylint: disable=arguments-differ
"""
Create the course_index in the db
"""
course_index['last_update'] = datetime.datetime.now(pytz.utc)
new_index = SplitModulestoreCourseIndex(**SplitModulestoreCourseIndex.fields_from_v1_schema(course_index))
new_index.save()
# TEMP: Also write to MongoDB, so we can switch back to using it if this new MySQL version doesn't work well:
super().insert_course_index(course_index, course_context, last_update_already_set=True)
def update_course_index(self, course_index, from_index=None, course_context=None): # pylint: disable=arguments-differ
"""
Update the db record for course_index.
Arguments:
from_index: If set, only update an index if it matches the one specified in `from_index`.
Exceptions:
SplitModulestoreCourseIndex.DoesNotExist: If the given object_id is not valid
"""
# "last_update not only tells us when this course was last updated but also helps prevent collisions"
# This code is just copying the behavior of the existing MongoPersistenceBackend
# See https://github.com/edx/edx-platform/pull/5200 for context
course_index['last_update'] = datetime.datetime.now(pytz.utc)
# Find the SplitModulestoreCourseIndex entry that we'll be updating:
index_obj = SplitModulestoreCourseIndex.objects.get(objectid=course_index["_id"])
# Check for collisions:
if from_index and index_obj.last_update != from_index["last_update"]:
# "last_update not only tells us when this course was last updated but also helps prevent collisions"
log.warning(
"Collision in Split Mongo when applying course index. This can happen in dev if django debug toolbar "
"is enabled, as it slows down parallel queries. New index was: %s",
course_index,
)
return # Collision; skip this update
# Apply updates to the index entry. While doing so, track which branch versions were changed (if any).
changed_branches = []
for attr, value in SplitModulestoreCourseIndex.fields_from_v1_schema(course_index).items():
if attr in ("objectid", "course_id"):
# Enforce these attributes as immutable.
if getattr(index_obj, attr) != value:
raise ValueError(
f"Attempted to change the {attr} key of a course index entry ({index_obj.course_id})"
)
else:
if attr.endswith("_version"):
# Model fields ending in _version are branches. If the branch version has changed, convert the field
# name to a branch name and report it in the history below.
if getattr(index_obj, attr) != value:
changed_branches.append(attr[:-8])
setattr(index_obj, attr, value)
if changed_branches:
# For the django simple history, indicate what was changed. Unfortunately at this point we only really know
# which branch(es) were changed, not anything more useful than that.
index_obj._change_reason = f'Updated {" and ".join(changed_branches)} branch' # pylint: disable=protected-access
# Save the course index entry and create a historical record:
index_obj.save()
# TEMP: Also write to MongoDB, so we can switch back to using it if this new MySQL version doesn't work well:
super().update_course_index(course_index, from_index, course_context, last_update_already_set=True)
def delete_course_index(self, course_key):
"""
Delete the course_index from the persistence mechanism whose id is the given course_index
"""
SplitModulestoreCourseIndex.objects.filter(course_id=course_key).delete()
# TEMP: Also write to MongoDB, so we can switch back to using it if this new MySQL version doesn't work well:
super().delete_course_index(course_key)
def _drop_database(self, database=True, collections=True, connections=True):
"""
Reset data for testing.
"""
try:
SplitModulestoreCourseIndex.objects.all().delete()
except TransactionManagementError as err:
# If the test doesn't use 'with self.allow_transaction_exception():', then this error can occur and it may
# be non-obvious why, so give a very clear explanation of how to fix it. See the docstring of
# allow_transaction_exception() for more details.
raise RuntimeError(
"post-test cleanup failed with TransactionManagementError. "
"Use 'with self.allow_transaction_exception():' from ModuleStoreTestCase/...IsolationMixin to fix it."
) from err
# TEMP: Also write to MongoDB, so we can switch back to using it if this new MySQL version doesn't work well:
super()._drop_database(database, collections, connections)

View File

@@ -101,7 +101,7 @@ from xmodule.modulestore.exceptions import (
VersionConflictError
)
from xmodule.modulestore.split_mongo import BlockKey, CourseEnvelope
from xmodule.modulestore.split_mongo.mongo_connection import DuplicateKeyError, DjangoFlexPersistenceBackend
from xmodule.modulestore.split_mongo.mongo_connection import DuplicateKeyError, MongoConnection
from xmodule.modulestore.store_utilities import DETACHED_XBLOCK_TYPES
from xmodule.partitions.partitions_service import PartitionService
@@ -651,7 +651,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
super().__init__(contentstore, **kwargs)
self.db_connection = DjangoFlexPersistenceBackend(**doc_store_config)
self.db_connection = MongoConnection(**doc_store_config)
if default_class is not None:
module_path, __, class_name = default_class.rpartition('.')

View File

@@ -12,7 +12,7 @@ from unittest.mock import patch
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.db import connections, transaction
from django.db import connections
from django.test import TestCase
from django.test.utils import override_settings
@@ -331,30 +331,6 @@ class ModuleStoreIsolationMixin(CacheIsolationMixin, SignalIsolationMixin):
cls.end_cache_isolation()
cls.enable_all_signals()
@staticmethod
def allow_transaction_exception():
"""
Context manager to wrap modulestore-using test code that may throw an exception.
(Use this if a modulestore test is failing with TransactionManagementError during cleanup.)
Details:
Some test cases that purposely throw an exception may normally cause the end_modulestore_isolation() cleanup
step to fail with
TransactionManagementError:
An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.
This happens because the test is wrapped in an implicit transaction and when the exception occurs, django won't
allow any subsequent database queries in the same transaction - in particular, the queries needed to clean up
split modulestore's SplitModulestoreCourseIndex table after the test.
By wrapping the inner part of the test in this atomic() call, we create a savepoint so that if an exception is
thrown, Django merely rolls back to the savepoint and the overall transaction continues, including the eventual
cleanup step.
This method mostly exists to provide this docstring/explanation; the code itself is trivial.
"""
return transaction.atomic()
class ModuleStoreTestUsersMixin():
"""

View File

@@ -23,21 +23,17 @@ class TestLibraries(MixedSplitTestCase):
def test_create_library(self):
"""
Test that we can create a library, and see how many database calls it uses to do so.
Test that we can create a library, and see how many mongo calls it uses to do so.
Expected mongo calls, in order:
-> insert(definition: {'block_type': 'library', 'fields': {}})
-> insert_structure(bulk)
-> insert_course_index(bulk)
find_one({'org': '...', 'run': 'library', 'course': '...'})
insert(definition: {'block_type': 'library', 'fields': {}})
Expected MySQL calls in order:
-> SELECT from SplitModulestoreCourseIndex case insensitive search for existing libraries
-> SELECT from SplitModulestoreCourseIndex lookup library with that exact ID
-> SELECT from XBlockConfiguration (?)
-> INSERT into SplitModulestoreCourseIndex to save the new library
-> INSERT a historical record of the SplitModulestoreCourseIndex
insert_structure(bulk)
insert_course_index(bulk)
get_course_index(bulk)
"""
with check_mongo_calls(0, 3), self.assertNumQueries(5):
with check_mongo_calls(2, 3):
LibraryFactory.create(modulestore=self.store)
def test_duplicate_library(self):

View File

@@ -366,9 +366,8 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# Draft:
# problem: One lookup to locate an item that exists
# fake: one w/ wildcard version
# split: has one lookup for the course and then one for the course items
# but the active_versions check is done in MySQL
@ddt.data((ModuleStoreEnum.Type.mongo, [1, 1], 0), (ModuleStoreEnum.Type.split, [1, 1], 0))
# split has one lookup for the course and then one for the course items
@ddt.data((ModuleStoreEnum.Type.mongo, [1, 1], 0), (ModuleStoreEnum.Type.split, [2, 2], 0))
@ddt.unpack
def test_has_item(self, default_ms, max_find, max_send):
self.initdb(default_ms)
@@ -391,17 +390,17 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# split:
# problem: active_versions, structure
# non-existent problem: ditto
@ddt.data((ModuleStoreEnum.Type.mongo, 0, [3, 2], 0), (ModuleStoreEnum.Type.split, 1, [1, 1], 0))
@ddt.data((ModuleStoreEnum.Type.mongo, [3, 2], 0), (ModuleStoreEnum.Type.split, [2, 2], 0))
@ddt.unpack
def test_get_item(self, default_ms, num_mysql, max_find, max_send):
def test_get_item(self, default_ms, max_find, max_send):
self.initdb(default_ms)
self._create_block_hierarchy()
with check_mongo_calls(max_find.pop(0), max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find.pop(0), max_send):
assert self.store.get_item(self.problem_x1a_1) is not None # lint-amnesty, pylint: disable=no-member
# try negative cases
with check_mongo_calls(max_find.pop(0), max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find.pop(0), max_send):
with pytest.raises(ItemNotFoundError):
self.store.get_item(self.fake_location)
@@ -412,16 +411,15 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# Draft:
# wildcard query, 6! load pertinent items for inheritance calls, load parents, course root fetch (why)
# Split:
# mysql: fetch course's active version from SplitModulestoreCourseIndex, spurious refetch x2
# find: get structure
@ddt.data((ModuleStoreEnum.Type.mongo, 0, 14, 0), (ModuleStoreEnum.Type.split, 3, 1, 0))
# active_versions (with regex), structure, and spurious active_versions refetch
@ddt.data((ModuleStoreEnum.Type.mongo, 14, 0), (ModuleStoreEnum.Type.split, 4, 0))
@ddt.unpack
def test_get_items(self, default_ms, num_mysql, max_find, max_send):
def test_get_items(self, default_ms, max_find, max_send):
self.initdb(default_ms)
self._create_block_hierarchy()
course_locn = self.course_locations[self.MONGO_COURSEID]
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
modules = self.store.get_items(course_locn.course_key, qualifiers={'category': 'problem'})
assert len(modules) == 6
@@ -515,16 +513,13 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
assert (orphan in [item.location for item in items_in_tree]) == orphan_in_items
assert len(items_in_tree) == expected_items_in_tree
# draft:
# find: get draft, get ancestors up to course (2-6), compute inheritance
# draft: get draft, get ancestors up to course (2-6), compute inheritance
# sends: update problem and then each ancestor up to course (edit info)
# split:
# mysql: SplitModulestoreCourseIndex - select 2x (by course_id, by objectid), update, update historical record
# find: definitions (calculator field), structures
# sends: 2 sends to update index & structure (note, it would also be definition if a content field changed)
@ddt.data((ModuleStoreEnum.Type.mongo, 0, 7, 5), (ModuleStoreEnum.Type.split, 4, 2, 2))
# split: active_versions, definitions (calculator field), structures
# 2 sends to update index & structure (note, it would also be definition if a content field changed)
@ddt.data((ModuleStoreEnum.Type.mongo, 7, 5), (ModuleStoreEnum.Type.split, 3, 2))
@ddt.unpack
def test_update_item(self, default_ms, num_mysql, max_find, max_send):
def test_update_item(self, default_ms, max_find, max_send):
"""
Update should succeed for r/w dbs
"""
@@ -534,7 +529,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# if following raised, then the test is really a noop, change it
assert problem.max_attempts != 2, 'Default changed making test meaningless'
problem.max_attempts = 2
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
problem = self.store.update_item(problem, self.user_id)
assert problem.max_attempts == 2, "Update didn't persist"
@@ -914,12 +909,11 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# get item (to delete subtree), get inheritance again.
# Sends: delete item, update parent
# Split
# mysql: SplitModulestoreCourseIndex - select 2x (by course_id, by objectid), update, update historical record
# Find: active_versions, 2 structures (published & draft), definition (unnecessary)
# Sends: updated draft and published structures and active_versions
@ddt.data((ModuleStoreEnum.Type.mongo, 0, 7, 2), (ModuleStoreEnum.Type.split, 4, 2, 3))
@ddt.data((ModuleStoreEnum.Type.mongo, 7, 2), (ModuleStoreEnum.Type.split, 3, 3))
@ddt.unpack
def test_delete_item(self, default_ms, num_mysql, max_find, max_send):
def test_delete_item(self, default_ms, max_find, max_send):
"""
Delete should reject on r/o db and work on r/w one
"""
@@ -928,7 +922,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
max_find += 1
with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, self.writable_chapter_location.course_key): # lint-amnesty, pylint: disable=line-too-long
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
self.store.delete_item(self.writable_chapter_location, self.user_id)
# verify it's gone
@@ -939,16 +933,15 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
self.store.get_item(self.writable_chapter_location, revision=ModuleStoreEnum.RevisionOption.published_only)
# Draft:
# find: find parent (definition.children), count versions of item, get parent, count grandparents,
# inheritance items, draft item, draft child, inheritance
# queries: find parent (definition.children), count versions of item, get parent, count grandparents,
# inheritance items, draft item, draft child, inheritance
# sends: delete draft vertical and update parent
# Split:
# mysql: SplitModulestoreCourseIndex - select 2x (by course_id, by objectid), update, update historical record
# find: draft and published structures, definition (unnecessary)
# queries: active_versions, draft and published structures, definition (unnecessary)
# sends: update published (why?), draft, and active_versions
@ddt.data((ModuleStoreEnum.Type.mongo, 0, 9, 2), (ModuleStoreEnum.Type.split, 4, 3, 3))
@ddt.data((ModuleStoreEnum.Type.mongo, 9, 2), (ModuleStoreEnum.Type.split, 4, 3))
@ddt.unpack
def test_delete_private_vertical(self, default_ms, num_mysql, max_find, max_send):
def test_delete_private_vertical(self, default_ms, max_find, max_send):
"""
Because old mongo treated verticals as the first layer which could be draft, it has some interesting
behavioral properties which this deletion test gets at.
@@ -979,7 +972,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
assert vert_loc in course.children
# delete the vertical and ensure the course no longer points to it
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
self.store.delete_item(vert_loc, self.user_id)
course = self.store.get_course(self.course_locations[self.MONGO_COURSEID].course_key, 0)
if hasattr(private_vert.location, 'version_guid'):
@@ -997,12 +990,11 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# find: find parent (definition.children) 2x, find draft item, get inheritance items
# send: one delete query for specific item
# Split:
# mysql: SplitModulestoreCourseIndex - select 2x (by course_id, by objectid), update, update historical record
# find: structure (cached)
# find: active_version & structure (cached)
# send: update structure and active_versions
@ddt.data((ModuleStoreEnum.Type.mongo, 0, 4, 1), (ModuleStoreEnum.Type.split, 4, 1, 2))
@ddt.data((ModuleStoreEnum.Type.mongo, 4, 1), (ModuleStoreEnum.Type.split, 2, 2))
@ddt.unpack
def test_delete_draft_vertical(self, default_ms, num_mysql, max_find, max_send):
def test_delete_draft_vertical(self, default_ms, max_find, max_send):
"""
Test deleting a draft vertical which has a published version.
"""
@@ -1032,23 +1024,23 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# test succeeds if delete succeeds w/o error
if default_ms == ModuleStoreEnum.Type.mongo and mongo_uses_error_check(self.store):
max_find += 1
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
self.store.delete_item(private_leaf.location, self.user_id)
# Draft:
# mysql: 1 select on SplitModulestoreCourseIndex since this searches both modulestores
# find: 1 find all courses (wildcard), 1 find to get each course 1 at a time (1 course)
# 1) find all courses (wildcard),
# 2) get each course 1 at a time (1 course),
# 3) wildcard split if it has any (1) but it doesn't
# Split:
# mysql: 3 selects on SplitModulestoreCourseIndex - 1 to get all courses, 2 to get specific course (this query is
# executed twice, possibly unnecessarily)
# find: 2 reads of structure, definition (s/b lazy; so, unnecessary),
# plus 1 wildcard find in draft mongo which has none
@ddt.data((ModuleStoreEnum.Type.mongo, 1, 2, 0), (ModuleStoreEnum.Type.split, 3, 3, 0))
# 1) wildcard split search,
# 2-4) active_versions, structure, definition (s/b lazy; so, unnecessary)
# 5) wildcard draft mongo which has none
@ddt.data((ModuleStoreEnum.Type.mongo, 3, 0), (ModuleStoreEnum.Type.split, 6, 0))
@ddt.unpack
def test_get_courses(self, default_ms, num_mysql, max_find, max_send):
def test_get_courses(self, default_ms, max_find, max_send):
self.initdb(default_ms)
# we should have one course across all stores
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
courses = self.store.get_courses()
course_ids = [course.location for course in courses]
assert len(courses) == 1, f'Not one course: {course_ids}'
@@ -1082,16 +1074,16 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
assert len(mongo_course.children) == 1
# draft is 2: find out which ms owns course, get item
# split: active_versions (mysql), structure, definition (to load course wiki string)
@ddt.data((ModuleStoreEnum.Type.mongo, 0, 2, 0), (ModuleStoreEnum.Type.split, 1, 2, 0))
# split: active_versions, structure, definition (to load course wiki string)
@ddt.data((ModuleStoreEnum.Type.mongo, 2, 0), (ModuleStoreEnum.Type.split, 3, 0))
@ddt.unpack
def test_get_course(self, default_ms, num_mysql, max_find, max_send):
def test_get_course(self, default_ms, max_find, max_send):
"""
This test is here for the performance comparison not functionality. It tests the performance
of getting an item whose scope.content fields are looked at.
"""
self.initdb(default_ms)
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
course = self.store.get_item(self.course_locations[self.MONGO_COURSEID])
assert course.id == self.course_locations[self.MONGO_COURSEID].course_key
@@ -1120,16 +1112,16 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# still only 2)
# Draft: get_parent
# Split: active_versions, structure
@ddt.data((ModuleStoreEnum.Type.mongo, 0, 1, 0), (ModuleStoreEnum.Type.split, 1, 1, 0))
@ddt.data((ModuleStoreEnum.Type.mongo, 1, 0), (ModuleStoreEnum.Type.split, 2, 0))
@ddt.unpack
def test_get_parent_locations(self, default_ms, num_mysql, max_find, max_send):
def test_get_parent_locations(self, default_ms, max_find, max_send):
"""
Test a simple get parent for a direct only category (i.e, always published)
"""
self.initdb(default_ms)
self._create_block_hierarchy()
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
parent = self.store.get_parent_location(self.problem_x1a_1) # lint-amnesty, pylint: disable=no-member
assert parent == self.vertical_x1a # lint-amnesty, pylint: disable=no-member
@@ -1637,10 +1629,10 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# 7-8. get sequential, compute inheritance
# 8-9. get vertical, compute inheritance
# 10-11. get other vertical_x1b (why?) and compute inheritance
# Split: loading structure from mongo (also loads active version from MySQL, not tracked here)
@ddt.data((ModuleStoreEnum.Type.mongo, 0, [12, 3], 0), (ModuleStoreEnum.Type.split, 1, [2, 1], 0))
# Split: active_versions & structure
@ddt.data((ModuleStoreEnum.Type.mongo, [12, 3], 0), (ModuleStoreEnum.Type.split, [3, 2], 0))
@ddt.unpack
def test_path_to_location(self, default_ms, num_mysql, num_finds, num_sends):
def test_path_to_location(self, default_ms, num_finds, num_sends):
"""
Make sure that path_to_location works
"""
@@ -1659,7 +1651,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
for location, expected in should_work:
# each iteration has different find count, pop this iter's find count
with check_mongo_calls(num_finds.pop(0), num_sends), self.assertNumQueries(num_mysql):
with check_mongo_calls(num_finds.pop(0), num_sends):
path = path_to_location(self.store, location)
assert path == expected
@@ -1876,10 +1868,10 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
assert False, "SplitMongoModuleStore was not found in MixedModuleStore"
# Draft: get all items which can be or should have parents
# Split: active_versions (mysql), structure (mongo)
@ddt.data((ModuleStoreEnum.Type.mongo, 0, 1, 0), (ModuleStoreEnum.Type.split, 1, 1, 0))
# Split: active_versions, structure
@ddt.data((ModuleStoreEnum.Type.mongo, 1, 0), (ModuleStoreEnum.Type.split, 2, 0))
@ddt.unpack
def test_get_orphans(self, default_ms, num_mysql, max_find, max_send):
def test_get_orphans(self, default_ms, max_find, max_send):
"""
Test finding orphans.
"""
@@ -1911,7 +1903,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
block_id=location.block_id
)
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
found_orphans = self.store.get_orphans(self.course_locations[self.MONGO_COURSEID].course_key)
self.assertCountEqual(found_orphans, orphan_locations)
@@ -2012,17 +2004,17 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
assert self.user_id == block.subtree_edited_by
assert datetime.datetime.now(UTC) > block.subtree_edited_on
# Draft: wildcard search of draft (find) and split (mysql)
# Split: wildcard search of draft (find) and split (mysql)
@ddt.data((ModuleStoreEnum.Type.mongo, 1, 1, 0), (ModuleStoreEnum.Type.split, 1, 1, 0))
# Draft: wildcard search of draft and split
# Split: wildcard search of draft and split
@ddt.data((ModuleStoreEnum.Type.mongo, 2, 0), (ModuleStoreEnum.Type.split, 2, 0))
@ddt.unpack
def test_get_courses_for_wiki(self, default_ms, num_mysql, max_find, max_send):
def test_get_courses_for_wiki(self, default_ms, max_find, max_send):
"""
Test the get_courses_for_wiki method
"""
self.initdb(default_ms)
# Test Mongo wiki
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
wiki_courses = self.store.get_courses_for_wiki('999')
assert len(wiki_courses) == 1
assert self.course_locations[self.MONGO_COURSEID].course_key.replace(branch=None) in wiki_courses
@@ -2036,18 +2028,13 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# 1. delete all of the published nodes in subtree
# 2. insert vertical as published (deleted in step 1) w/ the deleted problems as children
# 3-6. insert the 3 problems and 1 html as published
# Split:
# MySQL SplitModulestoreCourseIndex:
# 1. Select by course ID
# 2. Select by objectid
# 3-4. Update index version, update historical record
# Find: 2 structures (pre & post published?)
# Sends:
# 1. insert structure
# 2. write index entry
@ddt.data((ModuleStoreEnum.Type.mongo, 0, 2, 6), (ModuleStoreEnum.Type.split, 4, 2, 2))
# Split: active_versions, 2 structures (pre & post published?)
# Sends:
# - insert structure
# - write index entry
@ddt.data((ModuleStoreEnum.Type.mongo, 2, 6), (ModuleStoreEnum.Type.split, 3, 2))
@ddt.unpack
def test_unpublish(self, default_ms, num_mysql, max_find, max_send):
def test_unpublish(self, default_ms, max_find, max_send):
"""
Test calling unpublish
"""
@@ -2065,7 +2052,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
assert published_xblock is not None
# unpublish
with check_mongo_calls(max_find, max_send), self.assertNumQueries(num_mysql):
with check_mongo_calls(max_find, max_send):
self.store.unpublish(self.vertical_x1a, self.user_id) # lint-amnesty, pylint: disable=no-member
with pytest.raises(ItemNotFoundError):
@@ -2082,10 +2069,10 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
assert draft_xblock is not None
# Draft: specific query for revision None
# Split: active_versions from MySQL, structure from mongo
@ddt.data((ModuleStoreEnum.Type.mongo, 0, 1, 0), (ModuleStoreEnum.Type.split, 1, 1, 0))
# Split: active_versions, structure
@ddt.data((ModuleStoreEnum.Type.mongo, 1, 0), (ModuleStoreEnum.Type.split, 2, 0))
@ddt.unpack
def test_has_published_version(self, default_ms, mysql_queries, max_find, max_send):
def test_has_published_version(self, default_ms, max_find, max_send):
"""
Test the has_published_version method
"""
@@ -2095,7 +2082,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
# start off as Private
item = self.store.create_child(self.user_id, self.writable_chapter_location, 'problem', 'test_compute_publish_state') # lint-amnesty, pylint: disable=line-too-long
item_location = item.location
with self.assertNumQueries(mysql_queries), check_mongo_calls(max_find, max_send):
with check_mongo_calls(max_find, max_send):
assert not self.store.has_published_version(item)
# Private -> Public
@@ -3784,7 +3771,7 @@ class TestAsidesWithMixedModuleStore(CommonMixedModuleStoreSetup):
assert asides2[0].field11 == 'aside1_default_value1'
assert asides2[0].field12 == 'aside1_default_value2'
@ddt.data((ModuleStoreEnum.Type.mongo, 1, 0), (ModuleStoreEnum.Type.split, 1, 0))
@ddt.data((ModuleStoreEnum.Type.mongo, 1, 0), (ModuleStoreEnum.Type.split, 2, 0))
@XBlockAside.register_temp_plugin(AsideFoo, 'test_aside1')
@patch('xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types',
lambda self, block: ['test_aside1'])

View File

@@ -154,14 +154,14 @@ class CountMongoCallsCourseTraversal(TestCase):
(MIXED_OLD_MONGO_MODULESTORE_BUILDER, 0, True, False, 359),
# The line below shows the way this traversal *should* be done
# (if you'll eventually access all the fields and load all the definitions anyway).
(MIXED_SPLIT_MODULESTORE_BUILDER, None, False, True, 2),
(MIXED_SPLIT_MODULESTORE_BUILDER, None, True, True, 37),
(MIXED_SPLIT_MODULESTORE_BUILDER, 0, False, True, 37),
(MIXED_SPLIT_MODULESTORE_BUILDER, 0, True, True, 37),
(MIXED_SPLIT_MODULESTORE_BUILDER, None, False, False, 2),
(MIXED_SPLIT_MODULESTORE_BUILDER, None, True, False, 2),
(MIXED_SPLIT_MODULESTORE_BUILDER, 0, False, False, 2),
(MIXED_SPLIT_MODULESTORE_BUILDER, 0, True, False, 2),
(MIXED_SPLIT_MODULESTORE_BUILDER, None, False, True, 3),
(MIXED_SPLIT_MODULESTORE_BUILDER, None, True, True, 38),
(MIXED_SPLIT_MODULESTORE_BUILDER, 0, False, True, 38),
(MIXED_SPLIT_MODULESTORE_BUILDER, 0, True, True, 38),
(MIXED_SPLIT_MODULESTORE_BUILDER, None, False, False, 3),
(MIXED_SPLIT_MODULESTORE_BUILDER, None, True, False, 3),
(MIXED_SPLIT_MODULESTORE_BUILDER, 0, False, False, 3),
(MIXED_SPLIT_MODULESTORE_BUILDER, 0, True, False, 3)
)
@ddt.unpack
def test_number_mongo_calls(self, store_builder, depth, lazy, access_all_block_fields, num_mongo_calls):
@@ -178,7 +178,7 @@ class CountMongoCallsCourseTraversal(TestCase):
@ddt.data(
(MIXED_OLD_MONGO_MODULESTORE_BUILDER, 176),
(MIXED_SPLIT_MODULESTORE_BUILDER, 3),
(MIXED_SPLIT_MODULESTORE_BUILDER, 4),
)
@ddt.unpack
def test_lazy_when_course_previously_cached(self, store_builder, num_mongo_calls):

View File

@@ -163,7 +163,6 @@ class TestPublish(SplitWMongoCourseBootstrapper):
assert self.draft_mongo.has_item(other_child_loc), 'Oops, lost moved item'
@pytest.mark.django_db # required if using split modulestore
class DraftPublishedOpTestCourseSetup(unittest.TestCase):
"""
This class exists to test XML import and export between different modulestore

View File

@@ -53,7 +53,6 @@ TEST_ASSISTANT_USER_ID = ModuleStoreEnum.UserID.test - 12
@attr('mongo')
@pytest.mark.django_db
class SplitModuleTest(unittest.TestCase):
'''
The base set of tests manually populates a db w/ courses which have

View File

@@ -12,7 +12,7 @@ import ddt
from bson.objectid import ObjectId
from opaque_keys.edx.locator import CourseLocator
from xmodule.modulestore.split_mongo.mongo_connection import MongoPersistenceBackend
from xmodule.modulestore.split_mongo.mongo_connection import MongoConnection
from xmodule.modulestore.split_mongo.split import SplitBulkWriteMixin
VERSION_GUID_DICT = {
@@ -30,7 +30,7 @@ class TestBulkWriteMixin(unittest.TestCase): # lint-amnesty, pylint: disable=mi
self.bulk = SplitBulkWriteMixin()
self.bulk.SCHEMA_VERSION = 1
self.clear_cache = self.bulk._clear_cache = Mock(name='_clear_cache')
self.conn = self.bulk.db_connection = MagicMock(name='db_connection', spec=MongoPersistenceBackend)
self.conn = self.bulk.db_connection = MagicMock(name='db_connection', spec=MongoConnection)
self.conn.get_course_index.return_value = {'initial': 'index'}
self.course_key = CourseLocator('org', 'course', 'run-a', branch='test')

View File

@@ -1,4 +1,4 @@
""" Test the behavior of split_mongo/MongoPersistenceBackend """
""" Test the behavior of split_mongo/MongoConnection """
import unittest
@@ -8,7 +8,7 @@ import pytest
from pymongo.errors import ConnectionFailure
from xmodule.exceptions import HeartbeatFailure
from xmodule.modulestore.split_mongo.mongo_connection import MongoPersistenceBackend
from xmodule.modulestore.split_mongo.mongo_connection import MongoConnection
class TestHeartbeatFailureException(unittest.TestCase):
@@ -20,7 +20,7 @@ class TestHeartbeatFailureException(unittest.TestCase):
# pylint: disable=W0613
with patch('mongodb_proxy.MongoProxy') as mock_proxy:
mock_proxy.return_value.admin.command.side_effect = ConnectionFailure('Test')
useless_conn = MongoPersistenceBackend('useless', 'useless', 'useless')
useless_conn = MongoConnection('useless', 'useless', 'useless')
with pytest.raises(HeartbeatFailure):
useless_conn.heartbeat()

View File

@@ -19,7 +19,6 @@ from xmodule.x_module import XModuleMixin
@pytest.mark.mongo
@pytest.mark.django_db
class SplitWMongoCourseBootstrapper(unittest.TestCase):
"""
Helper for tests which need to construct split mongo & old mongo based courses to get interesting internal structure. # lint-amnesty, pylint: disable=line-too-long

View File

@@ -34,7 +34,6 @@ _LAST_WEEK = _TODAY - timedelta(days=7)
_NEXT_WEEK = _TODAY + timedelta(days=7)
@pytest.mark.django_db
class CourseMetadataUtilsTestCase(TestCase):
"""
Tests for course_metadata_utils.