How to restore a course run to a previous version

So you messed up a course’s content in Studio. This happens from time to time, often as the result of mistakenly importing content into the wrong course. Stay calm; if you act quickly and carefully, it should be pretty easy to fix.

This is only possible for “Split Mongo” courses, i.e. those whose course keys are in the form with course-v1:ORG+COURSE+RUN. It is not possible to restore a deprecated “Old Mongo” course, i.e. those whose course keys are in the form ORG/COURSE/RUN.

Preventing Deletion

Unfortunately, there is no way to undo destructive changes via the Studio UI. So, stop editing the course. Close Studio.

Studio saves a new “version” of a course every time you make an edit to it. This is what makes restoration possible. However, the longer you spend in the broken course, the more “versions” of the course Studio is likely to generate, thus pushing the ideal (pre-destructive-edit) version of the course further and further back in the version chain.

edXers: versions are more likely to be pruned (permanently deleted) if they are further back (~10 versions) in the version chain. Pruning generally occurs weekly on Sunday, via automated jobs (which can be paused as necessary). If it’s getting close to Sunday, then the urgency of this is higher.

edX engineers: Contact SRE and have them pause the modulestore pruning job for correct environment.

Communication

TODO: This section could use input from Partner Support.

You’ll want to alert your learner/partner support team(s) ASAP. If you’re not an engineer, you’ll want to contact one to perform the restoration (edXers: file a high-priority CR), and include a link to this guide in case the engineer has never restored a course before.

edXers: If this is outside of work hours and relates to a critical course run, it may be worth posting in #warroom and trying to get an engineer’s attention.

Restoration Steps

The restoration process has two steps: find the ID of the correct version, and then tell modulestore to point the course run at that version.

1. Find the correct version via the MongoDB shell

Currently, there’s no easy way to introspect course run versions via Studio. So, we need to dive into a MongoDB shell. Ideally, this shell should be read-only, as we don’t want to be making any edits to Mongo here by accident.

edXers: Log into the Read Replica and run /edx/bin/prod-edx-edxapp-mongo.sh (substituting prod for stage or edx for edge as necessary).

Once in the shell, run use edxapp. Then, copy this entire script in, substituting in the correct values for ORG, COURSE, and RUN at the top.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 var ORG = "..."; var COURSE = "..."; var RUN = "..."; function getLatestCourseStructure(org, course, run) { var courseIndex = db.modulestore.active_versions.findOne({ org: org, course: course, run: run }); return db.modulestore.structures.findOne({ _id: courseIndex.versions["draft-branch"] }); } function structureToCourseTitle(structure) { var rootCourseBlock = structure.blocks.find(function(block) { return ( block.block_type === structure.root[0] && block.block_id === structure.root[1] ) }); return rootCourseBlock.fields.display_name; } function examineStructure(structure) { return ( " version_guid = " + structure._id + "\n" + " title = " + structureToCourseTitle(structure) + "\n" + " edited_on = " + structure.edited_on ); } function lookBackNVersions(structure, numVersionsBack) { for (var i = 0; i < numVersionsBack; i++) { structure = db.modulestore.structures.findOne({ _id: structure.previous_version }) if (!structure) return null; } return structure; } function examineAllAvailableVersions(structure) { var output = ""; output += "Current version:\n"; output += examineStructure(structure) + "\n"; for (var i = 1; ; i++) { var olderStructure = lookBackNVersions(structure, i); if (!olderStructure) break; output += "\n"; output += i + " version(s) back:\n"; output += examineStructure(olderStructure) + "\n"; } output += "\n"; output += "Showing all available (non-pruned) versions." return output; } var latestCourseStructure = getLatestCourseStructure(ORG, COURSE, RUN); examineAllAvailableVersions(latestCourseStructure);

You should see an output something like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 > examineAllAvailableVersions(latestCourseStructure); Current version: version_guid = 5fbc16d7a9e30dc3e5a2eef4 title = Demonstration Course edited_on = Mon Nov 23 2020 20:08:55 GMT+0000 (UTC) 1 version(s) back: version_guid = 5f19c8940ed7e0e9d7044419 title = Demonstration Course edited_on = Thu Jul 23 2020 17:27:48 GMT+0000 (UTC) 2 version(s) back: version_guid = 5f19c8890ed7e0e9d7043a40 title = Demonstration Course edited_on = Thu Jul 23 2020 17:27:37 GMT+0000 (UTC) 3 version(s) back: version_guid = 5f19c8890ed7e0e9d7043a3a title = undefined edited_on = Thu Jul 23 2020 17:27:37 GMT+0000 (UTC) Showing all available (non-pruned) versions.

You can see here the different versions of the courses that exist in the Modulestore, along with their titles and creation dates.

Your goal here is to select the desired version. If you need more information, you can play with thegetLatestCourseStructure and lookBackNVersions functions yourself, each of which return big JSON documents detailing the structure of the course. This header comment that explains the DB structure of the Split modulestore may be useful to you.

Once you’ve chosen a version, make note of the version_guid you want to reset to, and use it in the next step.

2. Reset the course run to the correct version via the management command

There exists a management command to reset course content to different versions. It is implemented by reset_course_to_version in the Split modulestore. You may want to familiarize yourself a little bit with each of those before proceeding. Careful, there’s no confirmation prompt or anything.

1 2 3 4 5 6 7 8 9 10 11 # Replace the course key with your course key # and '5555aaaabbbbccccddddeeee' with your version_guid. $ ./manage.py cms reset_course_content course-v1:ORG+COURSE+RUN 5555aaaabbbbccccddddeeee ... ... (a ton of startup output) ... Resetting 'course-v1:ORG+COURSE+RUN' to version '5555aaaabbbbccccddddeeee'... ... ... (a ton of signal handler / Celery output) ... Done.

The command should run pretty quickly (<1s) and take effect immediately.

edXers: File an SRE support ticket and asking them to run this command for you, including the arguments.

Done?

Hopefully, that worked. All learner state should still safely be in the LMS, and should link back up to the restored content without any action. Huzzah!

If you hit roadblocks or found issues in this process, do comment to let us know.