Back to index

plone3  3.1.7
MigrationTool.py
Go to the documentation of this file.
00001 import logging
00002 import traceback
00003 import sys
00004 
00005 import transaction
00006 from zope.interface import implements
00007 
00008 from AccessControl import ClassSecurityInfo
00009 from Globals import InitializeClass, DTMLFile, DevelopmentMode
00010 from OFS.SimpleItem import SimpleItem
00011 from ZODB.POSException import ConflictError
00012 
00013 from Products.CMFCore.utils import UniqueObject, getToolByName
00014 from Products.CMFCore.utils import registerToolInterface
00015 from Products.CMFCore.permissions import ManagePortal, View
00016 from Products.CMFPlone.interfaces import IMigrationTool
00017 from Products.CMFPlone.PloneBaseTool import PloneBaseTool
00018 from Products.CMFPlone.utils import versionTupleFromString
00019 from Products.CMFPlone.utils import log, log_deprecated
00020 from AccessControl.requestmethod import postonly
00021 
00022 _upgradePaths = {}
00023 _widgetRegistry = {}
00024 
00025 class MigrationTool(PloneBaseTool, UniqueObject, SimpleItem):
00026     """Handles migrations between Plone releases"""
00027 
00028     implements(IMigrationTool)
00029 
00030     id = 'portal_migration'
00031     meta_type = 'Plone Migration Tool'
00032     toolicon = 'skins/plone_images/site_icon.gif'
00033 
00034     __implements__ = (PloneBaseTool.__implements__, SimpleItem.__implements__, )
00035 
00036     _needRecatalog = 0
00037     _needUpdateRole = 0
00038 
00039     manage_options = (
00040         { 'label' : 'Upgrade', 'action' : 'manage_migrate' },
00041         )
00042 
00043     security = ClassSecurityInfo()
00044 
00045     security.declareProtected(ManagePortal, 'manage_overview')
00046     security.declareProtected(ManagePortal, 'manage_results')
00047     security.declareProtected(ManagePortal, 'manage_migrate')
00048 
00049     manage_migrate = DTMLFile('www/migrationRun', globals())
00050     manage_overview = DTMLFile('www/migrationTool', globals())
00051     manage_results = DTMLFile('www/migrationResults', globals())
00052 
00053     # Add a visual note
00054     def om_icons(self):
00055         icons = ({
00056                     'path':'misc_/CMFPlone/tool.gif',
00057                     'alt':self.meta_type,
00058                     'title':self.meta_type,
00059                  },)
00060         if self.needUpgrading() \
00061            or self.needUpdateRole() \
00062            or self.needRecatalog():
00063             icons = icons + ({
00064                      'path':'misc_/PageTemplates/exclamation.gif',
00065                      'alt':'Error',
00066                      'title':'This Plone instance needs updating'
00067                   },)
00068 
00069         return icons
00070 
00071     ##############################################################
00072     # Public methods
00073     #
00074     # versions methods
00075 
00076     security.declareProtected(ManagePortal, 'getInstanceVersion')
00077     def getInstanceVersion(self):
00078         """ The version this instance of plone is on """
00079         if getattr(self, '_version', None) is None:
00080             self.setInstanceVersion(self.getFileSystemVersion())
00081         return self._version.lower()
00082 
00083     security.declareProtected(ManagePortal, 'setInstanceVersion')
00084     def setInstanceVersion(self, version):
00085         """ The version this instance of plone is on """
00086         self._version = version
00087 
00088     security.declareProtected(ManagePortal, 'knownVersions')
00089     def knownVersions(self):
00090         """All known version ids, except current one and unsupported
00091            migration paths.
00092         """
00093         versions = [k for k in _upgradePaths if _upgradePaths[k][1] != False]
00094         return versions
00095 
00096     security.declareProtected(ManagePortal, 'unsupportedVersion')
00097     def unsupportedVersion(self):
00098         """Is the current instance version known to be a no longer supported
00099            version for migrations.
00100         """
00101         versions = [k for k in _upgradePaths if _upgradePaths[k][1] is False]
00102         return self._version in versions
00103 
00104     security.declareProtected(ManagePortal, 'getFileSystemVersion')
00105     def getFileSystemVersion(self):
00106         """ The version this instance of plone is on """
00107         return self.Control_Panel.Products.CMFPlone.version.lower()
00108 
00109     security.declareProtected(View, 'getFSVersionTuple')
00110     def getFSVersionTuple(self):
00111         """ returns tuple representing filesystem version """
00112         v_str = self.getFileSystemVersion()
00113         return versionTupleFromString(v_str)
00114 
00115     security.declareProtected(View, 'getInstanceVersionTuple')
00116     def getInstanceVersionTuple(self):
00117         """ returns tuple representing instance version """
00118         v_str = self.getInstanceVersion()
00119         return versionTupleFromString(v_str)
00120 
00121     security.declareProtected(ManagePortal, 'needUpgrading')
00122     def needUpgrading(self):
00123         """ Need upgrading? """
00124         return self.getInstanceVersion() != self.getFileSystemVersion()
00125 
00126     security.declareProtected(ManagePortal, 'coreVersions')
00127     def coreVersions(self):
00128         """ Useful core information """
00129         vars = {}
00130         cp = self.Control_Panel
00131         vars['Zope'] = cp.version_txt
00132         vars['Python'] = cp.sys_version
00133         vars['Platform'] = cp.sys_platform
00134         vars['Plone Instance'] = self.getInstanceVersion()
00135         vars['Plone File System'] = self.getFileSystemVersion()
00136         vars['CMF'] = cp.Products.CMFCore.version
00137         vars['Debug mode'] = DevelopmentMode and 'Yes' or 'No'
00138         try:
00139             from PIL.Image import VERSION
00140         except ImportError:
00141             VERSION = ''
00142         vars['PIL'] = VERSION
00143         return vars
00144 
00145     security.declareProtected(ManagePortal, 'coreVersionsList')
00146     def coreVersionsList(self):
00147         """ Useful core information """
00148         res = self.coreVersions().items()
00149         res.sort()
00150         return res
00151 
00152     security.declareProtected(ManagePortal, 'needUpdateRole')
00153     def needUpdateRole(self):
00154         """ Do roles need to be updated? """
00155         return self._needUpdateRole
00156 
00157     security.declareProtected(ManagePortal, 'needRecatalog')
00158     def needRecatalog(self):
00159         """ Does this thing now need recataloging? """
00160         return self._needRecatalog
00161 
00162     security.declareProtected(ManagePortal,'getProductInfo')
00163     def getProductInfo(self):
00164         """Provide information about installed products for error reporting"""
00165         zope_products = self.getPhysicalRoot().Control_Panel.Products.objectValues()
00166         installed_products = getToolByName(self, 'portal_quickinstaller').listInstalledProducts(showHidden=1)
00167         products = {}
00168         for p in zope_products:
00169             product_info = {'id':p.id, 'version':p.version}
00170             for ip in installed_products:
00171                 if ip['id'] == p.id:
00172                     product_info['status'] = ip['status']
00173                     product_info['hasError'] = ip['hasError']
00174                     product_info['installedVersion'] = ip['installedVersion']
00175                     break
00176             products[p.id] = product_info
00177         return products
00178 
00179     security.declareProtected(ManagePortal,'getPILVersion')
00180     def getPILVersion(self):
00181         """The version of the installed Python Imaging Library."""
00182         log_deprecated("getPILVersion is deprecated and will be removed in "
00183                        "Plone 4.0. Please use coreVersions instead.")
00184         try:
00185             from PIL.Image import VERSION
00186         except ImportError:
00187             VERSION = None
00188         return VERSION
00189 
00190     security.declareProtected(ManagePortal, 'upgrade')
00191     def upgrade(self, REQUEST=None, dry_run=None, swallow_errors=1):
00192         """ perform the upgrade """
00193         # keep it simple
00194         out = []
00195 
00196         self._check()
00197 
00198         if dry_run:
00199             out.append(("Dry run selected.", logging.INFO))
00200 
00201         # either get the forced upgrade instance or the current instance
00202         newv = getattr(REQUEST, "force_instance_version",
00203                        self.getInstanceVersion())
00204 
00205         out.append(("Starting the migration from "
00206                     "version: %s" % newv, logging.INFO))
00207         while newv is not None:
00208             out.append(("Attempting to upgrade from: %s" % newv, logging.INFO))
00209             try:
00210                 newv, msgs = self._upgrade(newv)
00211                 if msgs:
00212                     for msg in msgs:
00213                         # if string make list
00214                         if isinstance(msg, basestring):
00215                             msg = [msg,]
00216                         # if no status, add one
00217                         if len(msg) == 1:
00218                             msg.append(logging.INFO)
00219                         out.append(msg)
00220                 if newv is not None:
00221                     out.append(("Upgrade to: %s, completed" % newv, logging.INFO))
00222                     self.setInstanceVersion(newv)
00223 
00224             except ConflictError:
00225                 raise
00226             except:
00227                 out.append(("Upgrade aborted", logging.ERROR))
00228                 out.append(("Error type: %s" % sys.exc_type, logging.ERROR))
00229                 out.append(("Error value: %s" % sys.exc_value, logging.ERROR))
00230                 for line in traceback.format_tb(sys.exc_traceback):
00231                     out.append((line, logging.ERROR))
00232 
00233                 # set newv to None
00234                 # to break the loop
00235                 newv = None
00236                 if not swallow_errors:
00237                     for msg, sev in out: log(msg, severity=sev)
00238                     raise
00239                 else:
00240                     # abort transaction to safe the zodb
00241                     transaction.abort()
00242 
00243         out.append(("End of upgrade path, migration has finished", logging.INFO))
00244 
00245         if self.needUpgrading():
00246             out.append((("The upgrade path did NOT reach "
00247                         "current version"), logging.ERROR))
00248             out.append(("Migration has failed", logging.ERROR))
00249         else:
00250             out.append((("Your ZODB and Filesystem Plone "
00251                          "instances are now up-to-date."), logging.INFO))
00252 
00253         # do this once all the changes have been done
00254         if self.needRecatalog():
00255             try:
00256                 catalog = self.portal_catalog
00257                 # Reduce threshold for the reindex run
00258                 old_threshold = catalog.threshold
00259                 pg_threshold = getattr(catalog, 'pgthreshold', 0)
00260                 catalog.pgthreshold = 300
00261                 catalog.threshold = 2000
00262                 catalog.refreshCatalog(clear=1)
00263                 catalog.threshold = old_threshold
00264                 catalog.pgthreshold = pg_threshold
00265                 self._needRecatalog = 0
00266             except ConflictError:
00267                 raise
00268             except:
00269                 out.append(("Exception was thrown while cataloging",
00270                             logging.ERROR))
00271                 for line in traceback.format_tb(sys.exc_traceback):
00272                     out.append((line, logging.ERROR))
00273                 if not swallow_errors:
00274                     for msg, sev in out: log(msg, severity=sev)
00275                     raise
00276 
00277         if self.needUpdateRole():
00278             try:
00279                 self.portal_workflow.updateRoleMappings()
00280                 self._needUpdateRole = 0
00281             except ConflictError:
00282                 raise
00283             except:
00284                 out.append((("Exception was thrown while updating "
00285                              "role mappings"), logging.ERROR))
00286                 for line in traceback.format_tb(sys.exc_traceback):
00287                     out.append((line, logging.ERROR))
00288                 if not swallow_errors:
00289                     for msg, sev in out: log(msg, severity=sev)
00290                     raise
00291 
00292         if dry_run:
00293             out.append(("Dry run selected, transaction aborted", logging.INFO))
00294             transaction.abort()
00295 
00296         # log all this
00297         for msg, sev in out: log(msg, severity=sev)
00298         try:
00299             return self.manage_results(self, out=out)
00300         except NameError:
00301             pass
00302     upgrade = postonly(upgrade)
00303 
00304     ##############################################################
00305     # Private methods
00306 
00307     def _check(self):
00308         """ Are we inside a Plone site?  Are we allowed? """
00309         if getattr(self,'portal_url', []) == []:
00310             raise AttributeError, 'You must be in a Plone site to migrate.'
00311 
00312     def _upgrade(self, version):
00313         version = version.lower()
00314         if not _upgradePaths.has_key(version):
00315             return None, ("Migration completed at version %s." % version,)
00316 
00317         newversion, function = _upgradePaths[version]
00318         # This means a now unsupported migration path has been triggered
00319         if function is False:
00320             return None, ("Migration stopped at version %s." % version,)
00321         res = function(self.aq_parent)
00322         return newversion, res
00323 
00324 def registerUpgradePath(oldversion, newversion, function):
00325     """ Basic register func """
00326     _upgradePaths[oldversion.lower()] = [newversion.lower(), function]
00327 
00328 InitializeClass(MigrationTool)
00329 registerToolInterface('portal_migration', IMigrationTool)