How to write the migrate code that will allow for a seamless upgrade process for users with an older code base
Introduction and Steps
If you have added new functionality to Zenoss that will break backwards compatibility, you need to provide code for your version that will allow users to upgrade without breakage.
Here's a breakdown of everything you will need to do in order to create your migration code and move your new code into production:
- Create your code in the $ZENHOME/Products/ZenModel/migrate/migrate package directory
- Add an import statement to __init__.py
- Run zenmigrate run iteratively to test
How It Works
The first place to look is in Products/ZenModel/migrate. For starters, examine the code in migrate.Migrate and note the Step class - this is what you will subclass when writing your migration code. The migrate.Migrate.Migration.main() method is what is called from the zenmigrate.py script and is what fires off the whole process.
To further understand the process, note the global variable allSteps: this is appended to every time Migrate.Step is instantiated.
But, you ask, how does my code get into allSteps?
Once your migration code is complete, you will do a couple things: add your file to the migrate directory and then add an import statement to migrate/__init__.py. When migrate.Migrate is imported in the zenmigrate.py script, the __init__.py code is run. Each module imported by this file has a class that gets instantiated at the end of its module (see the Migrate.Step.__init__() method). It is through this mechanism that each custom migration module in the migrate directory is added to allSteps (sorted by name and version number).
When migrate.Migrate.main() is called, allSteps is iterated and checks are performed to see if each migration step needs to be run or not. main() calls cutover(), which calls migrate(), and this is where the actual work of migration occurs, where your code gets executed.
What You Write
As noted, your migration code will subclass migrate.Migrate.Step. You can stub your migration file out like this:
__doc__='''
'''
__version__ = "$Revision$"[11:-2]
from Acquisition import aq_base
import Migrate
class MyMigrateCode(Migrate.Step):
version = Migrate.Version(1, 1, 0) # this needs to be updated to the appropriate version
def cutover(self, dmd):
pass
MyMigrateCode()
You will need to do the following to this code:
- fill in the doc string
- update the version passed to Migrate.Version
- update the cutover() method with actual code
- add any supporting code you might need that doesn't strictly belong in cutover()
Implement cutover()
Implementation is very straigh-forward: you get the dmd object passed into the cutover() method, thus giving you access to nearly every part of Zenoss. The only thing you don't have direct access to is the portal object. But you can easily get that by calling dmd.getPhysicalRoot().
Implementation details are 100% dependent upon what part of Zenoss you are migrating -- if you look at the current migration scripts (in trunk), you will get a good sense of the diversity as well as many examples from which to work.
Changes made to the zeo database (dmd and associated objects hierarchies) are not committed back to the database unless the --commit flag is passed to zenmigrate. This lets the developer repeatedly run a script and debug without making permanent changes to the database. If your migrate script makes changes outside of the zope database it should probably implement Step.revert() to undo any changes it has made.
Supporting Code
Supporting code is just modularization. If you're going to be using a funtion (or method) more than once, just break it out of the cutover() method. This will make maintenance easier and will allow those who come after you to see the intent of the migration code more quickly.
Testing and Deployment
Once your code meets with your approval (and that of the Zenoss development team), you are free to name it something appropriate and save it to Products/ZenModel/migrate. Upon adding your migration module, you must now edit Products/ZenModel/migrate/__init__.py so that it gets imported when zenmigrate.py is run.
After adding your script (and after every change you make to your new script), be sure to run zenmigrate run. Here are some things you can do to help ensure quality:
- Load Zenoss in a web browser, and navigate to the part of the application that was impacted by your migration script
- Look at the log files for error output
- Load up zendmd from the command line and make sure that no errors are generated when using the part of the API impacted by the change
After someone reviews the changes, your migration code is ready for deployment.