[CalendarServer-changes] [7304] CalendarServer/trunk/contrib/migration

source_changes at macosforge.org source_changes at macosforge.org
Fri Apr 8 12:29:46 PDT 2011


Revision: 7304
          http://trac.macosforge.org/projects/calendarserver/changeset/7304
Author:   sagen at apple.com
Date:     2011-04-08 12:29:46 -0700 (Fri, 08 Apr 2011)
Log Message:
-----------
Migration extra script now copies from sourceRoot, handles Lion -> Lion upgrades, and is much more unit-tested.

Modified Paths:
--------------
    CalendarServer/trunk/contrib/migration/calendarmigrator.py
    CalendarServer/trunk/contrib/migration/test/test_migrator.py

Modified: CalendarServer/trunk/contrib/migration/calendarmigrator.py
===================================================================
--- CalendarServer/trunk/contrib/migration/calendarmigrator.py	2011-04-08 19:21:22 UTC (rev 7303)
+++ CalendarServer/trunk/contrib/migration/calendarmigrator.py	2011-04-08 19:29:46 UTC (rev 7304)
@@ -29,7 +29,7 @@
 import subprocess
 import sys
 
-from plistlib import readPlist, writePlist
+from plistlib import readPlist, readPlistFromString, writePlist
 
 CALDAV_LAUNCHD_KEY = "org.calendarserver.calendarserver"
 CARDDAV_LAUNCHD_KEY = "org.addressbookserver.addressbookserver"
@@ -45,6 +45,7 @@
 RESOURCE_MIGRATION_TRIGGER = "trigger_resource_migration"
 SERVER_ADMIN = "/usr/sbin/serveradmin"
 LAUNCHCTL = "/bin/launchctl"
+DITTO = "/usr/bin/ditto"
 
 
 verbatimKeys = """
@@ -213,13 +214,50 @@
             if enableCalDAV:
                 unloadService(options, CALDAV_LAUNCHD_KEY)
 
-            newServerRootValue = migrateData(options)
-            migrateConfiguration(options, newServerRootValue, enableCalDAV,
-                enableCardDAV)
+            # Pull values out of previous plists
+            (
+                oldServerRootValue,
+                oldCalDocumentRootValue,
+                oldCalDataRootValue,
+                oldABDocumentRootValue,
+                uid,
+                gid
+            ) = examinePreviousSystem(
+                options.sourceRoot,
+                options.targetRoot
+            )
 
+            # Copy data as needed
+            (
+                newServerRoot,
+                newServerRootValue,
+                newDocumentRootValue,
+                newDataRootValue
+            ) = relocateData(
+                options.sourceRoot,
+                options.targetRoot,
+                oldServerRootValue,
+                oldCalDocumentRootValue,
+                oldCalDataRootValue,
+                oldABDocumentRootValue,
+                uid,
+                gid
+            )
+
+            # Combine old and new plists
+            migrateConfiguration(
+                options,
+                newServerRootValue,
+                newDocumentRootValue,
+                newDataRootValue,
+                enableCalDAV,
+                enableCardDAV
+            )
+
             configureNotifications()
 
-            triggerResourceMigration(newServerRootValue)
+            triggerResourceMigration(newServerRoot)
+
             setRunState(options, enableCalDAV, enableCardDAV)
 
     else:
@@ -309,7 +347,8 @@
         open(triggerPath, "w").close()
 
 
-def migrateConfiguration(options, newServerRootValue, enableCalDAV, enableCardDAV):
+def migrateConfiguration(options, newServerRootValue,
+    newDocumentRootValue, newDataRootValue, enableCalDAV, enableCardDAV):
     """
     Copy files/directories/symlinks from previous system's /etc/caldavd
     and /etc/carddavd
@@ -324,7 +363,6 @@
         log("New configuration directory does not exist: %s" % (newConfigDir,))
         return
 
-
     for configDir in (CALDAVD_CONFIG_DIR, CARDDAVD_CONFIG_DIR):
 
         oldConfigDir = os.path.join(options.sourceRoot, configDir)
@@ -386,8 +424,8 @@
     mergePlist(oldCalDAVDPlist, oldCardDAVDPlist, newCalDAVDPlist)
 
     newCalDAVDPlist["ServerRoot"] = newServerRootValue
-    newCalDAVDPlist["DocumentRoot"] = "Documents"
-    newCalDAVDPlist["DataRoot"] = "Data"
+    newCalDAVDPlist["DocumentRoot"] = newDocumentRootValue
+    newCalDAVDPlist["DataRoot"] = newDataRootValue
 
     newCalDAVDPlist["EnableCalDAV"] = enableCalDAV
     newCalDAVDPlist["EnableCardDAV"] = enableCardDAV
@@ -515,250 +553,211 @@
 
 def log(msg):
     try:
+        timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
+        msg = "calendarmigrator: %s %s" % (timestamp, msg)
+        print msg # so it appears in Setup.log
         with open(LOG, 'a') as output:
-            timestamp = datetime.datetime.now().strftime("%b %d %H:%M:%S")
-            msg = "calendarmigrator: %s %s" % (timestamp, msg)
             output.write("%s\n" % (msg,)) # so it appears in our log
-            print msg # so it appears in Setup.log
     except IOError:
         # Could not write to log
         pass
 
-def migrateData(options):
+def examinePreviousSystem(sourceRoot, targetRoot, diskAccessor=None):
     """
     Examines the old caldavd.plist and carddavd.plist to see where data
-    lives in the previous system.  If there is old data, calls relocateData( )
+    lives in the previous system.
     """
 
-    oldCalDocuments = None
-    oldCalData = None
-    oldABDocuments = None
-    calendarDataInDefaultLocation = True
-    addressbookDataInDefaultLocation = True
-    uid = -1
-    gid = -1
-    newServerRoot = None # actual path
-    newServerRootValue = NEW_SERVER_ROOT # value to put in plist
+    if diskAccessor is None:
+        diskAccessor = DiskAccessor()
 
-    oldCalConfigDir = os.path.join(options.sourceRoot, CALDAVD_CONFIG_DIR)
+    oldServerRootValue = None
+    oldCalDocumentRootValue = None
+    oldCalDataRootValue = None
+    oldABDocumentRootValue = None
+
+    # Get uid and gid from new caldavd.plist
+    newCalConfigDir = os.path.join(targetRoot, CALDAVD_CONFIG_DIR)
+    newCalPlistPath = os.path.join(newCalConfigDir, CALDAVD_PLIST)
+    if diskAccessor.exists(newCalPlistPath):
+        contents = diskAccessor.readFile(newCalPlistPath)
+        newCalPlist = readPlistFromString(contents)
+        uid, gid = getServerIDs(newCalPlist)
+        log("ServerIDs from %s: %d, %d" % (newCalPlistPath, uid, gid))
+    else:
+        uid = gid = -1
+        log("Can't find new calendar plist at %s" % (newCalPlistPath,))
+
+    # Try and read old caldavd.plist
+    oldCalConfigDir = os.path.join(sourceRoot, CALDAVD_CONFIG_DIR)
     oldCalPlistPath = os.path.join(oldCalConfigDir, CALDAVD_PLIST)
-    if os.path.exists(oldCalPlistPath):
-        oldCalPlist = readPlist(oldCalPlistPath)
-        uid, gid = getServerIDs(oldCalPlist)
-        log("ServerIDs: %d, %d" % (uid, gid))
+    if diskAccessor.exists(oldCalPlistPath):
+        contents = diskAccessor.readFile(oldCalPlistPath)
+        oldCalPlist = readPlistFromString(contents)
+        log("Found previous caldavd plist at %s" % (oldCalPlistPath,))
+
+        oldServerRootValue = oldCalPlist.get("ServerRoot", None)
+        oldCalDocumentRootValue = oldCalPlist.get("DocumentRoot", None)
+        oldCalDataRootValue = oldCalPlist.get("DataRoot", None)
+
     else:
         log("Can't find previous calendar plist at %s" % (oldCalPlistPath,))
         oldCalPlist = None
-        newCalConfigDir = os.path.join(options.targetRoot, CALDAVD_CONFIG_DIR)
-        newCalPlistPath = os.path.join(newCalConfigDir, CALDAVD_PLIST)
-        if os.path.exists(newCalPlistPath):
-            newCalPlist = readPlist(newCalPlistPath)
-            uid, gid = getServerIDs(newCalPlist)
-            log("ServerIDs: %d, %d" % (uid, gid))
 
+    # Try and read old carddavd.plist
+    oldABConfigDir = os.path.join(sourceRoot, CARDDAVD_CONFIG_DIR)
+    oldABPlistPath = os.path.join(oldABConfigDir, CARDDAVD_PLIST)
+    if diskAccessor.exists(oldABPlistPath):
+        contents = diskAccessor.readFile(oldABPlistPath)
+        oldABPlist = readPlistFromString(contents)
+        log("Found previous carddavd plist at %s" % (oldABPlistPath,))
 
-    oldABConfigDir = os.path.join(options.sourceRoot, CARDDAVD_CONFIG_DIR)
-    oldABPlistPath = os.path.join(oldABConfigDir, CARDDAVD_PLIST)
-    if os.path.exists(oldABPlistPath):
-        oldABPlist = readPlist(oldABPlistPath)
+        oldABDocumentRootValue = oldABPlist.get("DocumentRoot", None)
     else:
-        log("Can't find previous addressbook plist at %s" % (oldABPlistPath,))
+        log("Can't find previous carddavd plist at %s" % (oldABPlistPath,))
         oldABPlist = None
 
-    if oldCalPlist is not None:
-        # See if there is actually any calendar data
+    return (
+        oldServerRootValue,
+        oldCalDocumentRootValue,
+        oldCalDataRootValue,
+        oldABDocumentRootValue,
+        uid,
+        gid
+    )
 
-        oldDocumentRoot = oldCalPlist["DocumentRoot"]
-        if oldDocumentRoot.rstrip("/") != "/Library/CalendarServer/Documents":
-            log("Calendar data in non-standard location: %s" % (oldDocumentRoot,))
-            calendarDataInDefaultLocation = False
-        else:
-            log("Calendar data in standard location: %s" % (oldDocumentRoot,))
 
-        oldDataRoot = oldCalPlist["DataRoot"]
+def relocateData(sourceRoot, targetRoot, oldServerRootValue,
+    oldCalDocumentRootValue, oldCalDataRootValue, oldABDocumentRootValue,
+    uid, gid, diskAccessor=None):
+    """
+    Copy data from sourceRoot to targetRoot, except when data is on another
+    volume in which case we just refer to it there.
+    """
 
-        oldCalendarsPath = os.path.join(oldDocumentRoot, "calendars")
-        if os.path.exists(oldCalendarsPath):
-            # There is calendar data
-            oldCalDocuments = oldDocumentRoot
-            oldCalData = oldDataRoot
-            log("Calendar data to migrate from %s and %s" %
-                (oldCalDocuments, oldCalData))
+    if diskAccessor is None:
+        diskAccessor = DiskAccessor()
 
-            if calendarDataInDefaultLocation:
-                newServerRoot = absolutePathWithRoot(options.targetRoot,
-                    NEW_SERVER_ROOT)
-                newServerRootValue = NEW_SERVER_ROOT
-            else:
-                newServerRoot = absolutePathWithRoot(options.targetRoot,
-                    oldDocumentRoot)
-                newServerRootValue = oldDocumentRoot
-        else:
-            log("No calendar data to migrate")
+    log("RelocateData: sourceRoot=%s, targetRoot=%s, oldServerRootValue=%s, oldCalDocumentRootValue=%s, oldCalDataRootValue=%s, oldABDocumentRootValue=%s, uid=%d, gid=%d" % (sourceRoot, targetRoot, oldServerRootValue, oldCalDocumentRootValue, oldCalDataRootValue, oldABDocumentRootValue, uid, gid))
 
-    if oldABPlist is not None:
-        # See if there is actually any addressbook data
 
-        oldDocumentRoot = oldABPlist["DocumentRoot"]
-        if oldDocumentRoot.rstrip("/") != "/Library/AddressBookServer/Documents":
-            log("AddressBook data in non-standard location: %s" % (oldDocumentRoot,))
-            addressbookDataInDefaultLocation = False
+    if oldServerRootValue:
+        newServerRootValue = oldServerRootValue
+        # Source is Lion; see if ServerRoot refers to an external volume
+        # or a directory in sourceRoot
+        if diskAccessor.exists(oldServerRootValue):
+            # refers to an external volume
+            newServerRoot = newServerRootValue
+        elif diskAccessor.exists(os.path.join(sourceRoot, oldServerRootValue)):
+            # refers to a directory on sourceRoot
+            newServerRoot = absolutePathWithRoot(targetRoot, newServerRootValue)
         else:
-            log("AddressBook data in standard location: %s" % (oldDocumentRoot,))
+            # It doesn't exist, so use default
+            newServerRootValue = NEW_SERVER_ROOT
+            newServerRoot = absolutePathWithRoot(targetRoot, newServerRootValue)
 
-        oldAddressbooksPath = os.path.join(oldDocumentRoot, "addressbooks")
-        if os.path.exists(oldAddressbooksPath):
-            # There is addressbook data
-            oldABDocuments = oldDocumentRoot
-            log("AddressBook data to migrate from %s" % (oldABDocuments,))
+        # If there was an old ServerRoot value, process DocumentRoot and
+        # DataRoot because those could be relative to ServerRoot
+        oldCalDocumentRootValueProcessed = os.path.join(oldServerRootValue,
+            oldCalDocumentRootValue)
+        oldCalDataRootValueProcessed = os.path.join(oldServerRootValue,
+            oldCalDataRootValue)
+    else:
+        newServerRootValue = NEW_SERVER_ROOT
+        newServerRoot = absolutePathWithRoot(targetRoot, newServerRootValue)
+        oldCalDocumentRootValueProcessed = oldCalDocumentRootValue
+        oldCalDataRootValueProcessed = oldCalDataRootValue
 
-            if newServerRoot is None:
-                # don't override server root computed from calendar
-                if addressbookDataInDefaultLocation:
-                    newServerRoot = absolutePathWithRoot(options.targetRoot,
-                        NEW_SERVER_ROOT)
-                    newServerRootValue = NEW_SERVER_ROOT
-                else:
-                    newServerRoot = absolutePathWithRoot(options.targetRoot,
-                        oldDocumentRoot)
-                    newServerRootValue = oldDocumentRoot
-        else:
-            log("No addressbook data to migrate")
+    # Set default values for these, possibly overridden below:
+    newDocumentRootValue = "Documents"
+    newDocumentRoot = absolutePathWithRoot(
+        targetRoot,
+        os.path.join(newServerRootValue, newDocumentRootValue)
+    )
+    newDataRootValue = "Data"
+    newDataRoot = absolutePathWithRoot(
+        targetRoot,
+        os.path.join(newServerRootValue, newDataRootValue)
+    )
 
-    if (oldCalDocuments or oldABDocuments) and newServerRoot:
-        relocateData(oldCalDocuments, oldCalData, oldABDocuments, uid, gid,
-            calendarDataInDefaultLocation, addressbookDataInDefaultLocation,
-            newServerRoot)
+    # Old Calendar DocumentRoot
+    if oldCalDocumentRootValueProcessed:
+        if diskAccessor.exists(oldCalDocumentRootValueProcessed):
+            # Must be on an external volume if we see it existing at the point
+            # so don't copy it
+            newDocumentRoot = newDocumentRootValue = oldCalDocumentRootValueProcessed
+        elif diskAccessor.exists(absolutePathWithRoot(sourceRoot, oldCalDocumentRootValueProcessed)):
+            diskAccessor.ditto(
+                absolutePathWithRoot(sourceRoot, oldCalDocumentRootValueProcessed),
+                newDocumentRoot
+            )
+            diskAccessor.chown(newDocumentRoot, uid, gid, recursive=True)
 
-    return newServerRootValue
+    # Old Calendar DataRoot
+    if oldCalDataRootValueProcessed:
+        if diskAccessor.exists(oldCalDataRootValueProcessed):
+            # Must be on an external volume if we see it existing at the point
+            # so don't copy it
+            newDataRootValue = oldCalDataRootValueProcessed
+        elif diskAccessor.exists(
+            absolutePathWithRoot(sourceRoot, oldCalDataRootValueProcessed)
+        ):
+            diskAccessor.ditto(
+                absolutePathWithRoot(sourceRoot, oldCalDataRootValueProcessed),
+                newDataRoot
+            )
+            diskAccessor.chown(newDataRoot, uid, gid, recursive=True)
 
-def relocateData(oldCalDocuments, oldCalData, oldABDocuments, uid, gid,
-    calendarDataInDefaultLocation, addressbookDataInDefaultLocation,
-    newServerRoot):
-    """
-    Relocates existing calendar data to the new default location iff the data
-    was previously in the old default location; otherwise the old calendar
-    DocumentRoot becomes the new ServerRoot directory, the contents of the
-    old DocumentRoot are moved into ServerRoot/Documents and the contents of
-    old DataRoot are copied/moved into ServerRoot/Data.  If there is addressbook
-    data, a symlink is created as ServerRoot/Documents/addressbooks pointing
-    to the old addressbook directory so that the import-to-PostgreSQL will
-    find it.
-    """
+    # Old AddressBook DocumentRoot
+    if oldABDocumentRootValue:
+        newAddressBooks = os.path.join(newDocumentRoot, "addressbooks")
+        if diskAccessor.exists(oldABDocumentRootValue):
+            # Must be on an external volume if we see it existing at the point
+            diskAccessor.ditto(
+                os.path.join(oldABDocumentRootValue, "addressbooks"),
+                newAddressBooks
+            )
+        elif diskAccessor.exists(
+            absolutePathWithRoot(sourceRoot, oldABDocumentRootValue)
+        ):
+            diskAccessor.ditto(
+                absolutePathWithRoot(
+                    sourceRoot,
+                    os.path.join(oldABDocumentRootValue, "addressbooks")
+                ),
+                os.path.join(newDocumentRoot, "addressbooks")
+            )
+        if diskAccessor.exists(newAddressBooks):
+            diskAccessor.chown(newAddressBooks, uid, gid, recursive=True)
 
-    log("RelocateData: cal documents=%s, cal data=%s, ab documents=%s, new server root=%s"
-        % (oldCalDocuments, oldCalData, oldABDocuments, newServerRoot))
 
-    if oldCalDocuments and os.path.exists(oldCalDocuments):
+    # Change ownership of newServerRoot
+    if newServerRoot and diskAccessor.exists(newServerRoot):
+        diskAccessor.chown(newServerRoot, uid, gid, recursive=False)
 
-        if calendarDataInDefaultLocation:
-            # We're in the default location, relocate to new location
-            newCalDocuments = os.path.join(newServerRoot, "Documents")
-            if not os.path.exists(newCalDocuments):
-                os.mkdir(newCalDocuments)
-            newCalData = os.path.join(newServerRoot, "Data")
-            if not os.path.exists(newCalData):
-                os.mkdir(newCalData)
-            if os.path.exists(oldCalDocuments):
-                # Move evertying from oldCalDocuments
-                for item in list(os.listdir(oldCalDocuments)):
-                    source = os.path.join(oldCalDocuments, item)
-                    dest = os.path.join(newCalDocuments, item)
-                    log("Relocating %s to %s" % (source, dest))
-                    os.rename(source, dest)
-            else:
-                log("Warning: %s does not exist; nothing to migrate" % (oldCalDocuments,))
-        else:
-            # The admin has moved calendar data to a non-standard location so
-            # we're going to leave it there, but move things down a level so
-            # that the old DocumentRoot becomes new ServerRoot
+    newServerRootValue, newDocumentRootValue = relativize(newServerRootValue,
+        newDocumentRootValue)
+    newServerRootValue, newDataRootValue = relativize(newServerRootValue,
+        newDataRootValue)
 
-            # Create "Documents" directory with same ownership as oldCalDocuments
-            newCalDocuments = os.path.join(newServerRoot, "Documents")
-            log("New documents directory: %s" % (newCalDocuments,))
-            newCalData = os.path.join(newServerRoot, "Data")
-            log("New data directory: %s" % (newCalData,))
-            os.mkdir(newCalDocuments)
-            os.mkdir(newCalData)
-            for item in list(os.listdir(newServerRoot)):
-                if item not in ("Documents", "Data"):
-                    source = os.path.join(newServerRoot, item)
-                    dest = os.path.join(newCalDocuments, item)
-                    log("Relocating %s to %s" % (source, dest))
-                    os.rename(source, dest)
+    return (
+        newServerRoot,
+        newServerRootValue,
+        newDocumentRootValue,
+        newDataRootValue
+    )
 
-        # Relocate calendar DataRoot, copying all files
-        if os.path.exists(oldCalData):
-            if not os.path.exists(newCalData):
-                os.mkdir(newCalData)
-            for item in list(os.listdir(oldCalData)):
-                source = os.path.join(oldCalData, item)
-                if not os.path.isfile(source):
-                    continue
-                dest = os.path.join(newCalData, item)
-                log("Relocating %s to %s" % (source, dest))
-                try:
-                    os.rename(source, dest)
-                except OSError:
-                    # Can't rename because it's cross-volume; must copy/delete
-                    shutil.copy2(source, dest)
-                    os.remove(source)
 
-        # Symlink to AB document root so server will find it an import to
-        # PostgreSQL
-        if oldABDocuments and os.path.exists(oldABDocuments):
-            oldAddressBooks = os.path.join(oldABDocuments, "addressbooks")
-            newAddressBooks = os.path.join(newCalDocuments, "addressbooks")
-            log("Symlinking AddressBook data: %s to %s" % (newAddressBooks, oldAddressBooks))
-            os.symlink(oldAddressBooks, newAddressBooks)
+def relativize(parent, child):
+    """
+    If child is really a child of parent, make child relative to parent.
+    """
+    if child.startswith(parent):
+        parent = parent.rstrip("/")
+        child = child[len(parent):].strip("/")
+    return parent.rstrip("/"), child.rstrip("/")
 
 
-    elif oldABDocuments and os.path.exists(oldABDocuments):
-        # No calendar data, only addressbook data
-
-        if addressbookDataInDefaultLocation:
-            # We're in the default location, relocate to new location
-            newABDocuments = os.path.join(newServerRoot, "Documents")
-            if os.path.exists(newABDocuments):
-                # Move evertying from oldABDocuments
-                for item in list(os.listdir(oldABDocuments)):
-                    source = os.path.join(oldABDocuments, item)
-                    dest = os.path.join(newABDocuments, item)
-                    log("Relocating %s to %s" % (source, dest))
-                    os.rename(source, dest)
-            else:
-                log("Error: %s does not exist" % (newABDocuments,))
-        else:
-            # The admin has moved addressbook data to a non-standard location so
-            # we're going to leave it there, but move things down a level so
-            # that the old DocumentRoot becomes new ServerRoot
-
-            # Create "Documents" directory with same ownership as oldABDocuments
-            newABDocuments = os.path.join(newServerRoot, "Documents")
-            newABData = os.path.join(newServerRoot, "Data")
-            log("New documents directory: %s" % (newABDocuments,))
-            os.mkdir(newABDocuments)
-            os.mkdir(newABData)
-            for item in list(os.listdir(newServerRoot)):
-                if item not in ("Documents", "Data"):
-                    source = os.path.join(newServerRoot, item)
-                    dest = os.path.join(newABDocuments, item)
-                    log("Relocating %s to %s" % (source, dest))
-                    os.rename(source, dest)
-
-    if newServerRoot and os.path.exists(newServerRoot):
-        """
-        Change onwnership of entire ServerRoot
-        """
-        os.chown(newServerRoot, uid, gid)
-        for root, dirs, files in os.walk(newServerRoot, followlinks=True):
-            for name in dirs:
-                os.chown(os.path.join(root, name), uid, gid)
-            for name in files:
-                os.chown(os.path.join(root, name), uid, gid)
-
-
-
 def getServerIDs(plist):
     """
     Given a caldavd.plist, return the userid and groupid for the UserName and
@@ -772,6 +771,7 @@
         gid = grp.getgrnam(plist["GroupName"]).gr_gid
     return uid, gid
 
+
 def absolutePathWithRoot(root, path):
     """
     Combine root and path as long as path does not start with /Volumes/
@@ -782,5 +782,59 @@
         path = path.strip("/")
         return os.path.join(root, path)
 
+
+class DiskAccessor(object):
+    """
+    A wrapper around various disk access methods so that unit tests can easily
+    replace these with a stub that doesn't actually require disk access.
+    """
+
+    def exists(self, path):
+        return os.path.exists(path)
+
+    def readFile(self, path):
+        input = file(path)
+        contents = input.read()
+        input.close()
+        return contents
+
+    def mkdir(self, path):
+        return os.mkdir(path)
+
+    def rename(self, before, after):
+        try:
+            return os.rename(before, after)
+        except OSError:
+            # Can't rename because it's cross-volume; must copy/delete
+            shutil.copy2(before, after)
+            return os.remove(before)
+
+    def isfile(self, path):
+        return os.path.isfile(path)
+
+    def symlink(self, orig, link):
+        return os.symlink(orig, link)
+
+    def chown(self, path, uid, gid, recursive=False):
+        os.chown(path, uid, gid)
+        if recursive:
+            for root, dirs, files in os.walk(path, followlinks=True):
+                for name in dirs:
+                    os.chown(os.path.join(root, name), uid, gid)
+                for name in files:
+                    os.chown(os.path.join(root, name), uid, gid)
+
+
+    def walk(self, path, followlinks=True):
+        return os.walk(path, followlinks=followlinks)
+
+    def listdir(self, path):
+        return list(os.listdir(path))
+
+    def ditto(self, src, dest):
+        log("Copying with ditto: %s to %s" % (src, dest))
+        return subprocess.call([DITTO, src, dest])
+
+
 if __name__ == '__main__':
     main()

Modified: CalendarServer/trunk/contrib/migration/test/test_migrator.py
===================================================================
--- CalendarServer/trunk/contrib/migration/test/test_migrator.py	2011-04-08 19:21:22 UTC (rev 7303)
+++ CalendarServer/trunk/contrib/migration/test/test_migrator.py	2011-04-08 19:29:46 UTC (rev 7304)
@@ -15,13 +15,21 @@
 ##
 
 import twistedcaldav.test.util
-from contrib.migration.calendarmigrator import mergePlist
+from contrib.migration.calendarmigrator import (
+    mergePlist, examinePreviousSystem, relocateData, relativize
+)
+import contrib.migration.calendarmigrator
 
 class MigrationTests(twistedcaldav.test.util.TestCase):
     """
     Calendar Server Migration Tests
     """
 
+    def setUp(self):
+        # Disable logging during tests
+        self.patch(contrib.migration.calendarmigrator, "log", lambda _: None)
+
+
     def test_mergeSSL(self):
 
         # SSL on for both services
@@ -237,6 +245,33 @@
         mergePlist(oldCalDAV, oldCardDAV, newCombined)
         self.assertEquals(newCombined, expected)
 
+        # Only CalDAV (Lion -> Lion)
+        oldCalDAV = {
+            "BindHTTPPorts": [],
+            "BindSSLPorts": [],
+            "HTTPPort": 8008,
+            "RedirectHTTPToHTTPS": False,
+            "SSLAuthorityChain": "/etc/certificates/test.chain.pem",
+            "SSLCertificate": "/etc/certificates/test.cert.pem",
+            "SSLPort": 8443,
+            "SSLPrivateKey": "/etc/certificates/test.key.pem",
+        }
+        oldCardDAV = {
+        }
+        expected = {
+            "BindHTTPPorts": [8008, 8800],
+            "BindSSLPorts": [8443, 8843],
+            "EnableSSL" : True,
+            "HTTPPort": 8008,
+            "RedirectHTTPToHTTPS": True,
+            "SSLAuthorityChain": "/etc/certificates/test.chain.pem",
+            "SSLCertificate": "/etc/certificates/test.cert.pem",
+            "SSLPort": 8443,
+            "SSLPrivateKey": "/etc/certificates/test.key.pem",
+        }
+        newCombined = { }
+        mergePlist(oldCalDAV, oldCardDAV, newCombined)
+        self.assertEquals(newCombined, expected)
 
 
         # All settings missing!
@@ -256,3 +291,1061 @@
         newCombined = { }
         mergePlist(oldCalDAV, oldCardDAV, newCombined)
         self.assertEquals(newCombined, expected)
+
+
+    def test_examinePreviousSystem(self):
+        """
+        Set up a virtual system in various configurations, then ensure the
+        examinePreviousSystem( ) method detects/returns the expected values.
+
+        'info' is an array of tuples, each tuple containing:
+            - Description of configuration
+            - Layout of disk as a dictionary of paths plus file contents
+            - Expected return values
+        """
+
+        info = [
+
+        (
+            "Snow -> Lion Migration, all in default locations",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/Library/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/old/Library/CalendarServer/Data/" : True,
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+            },
+            (
+                None, # Old ServerRoot value
+                "/Library/CalendarServer/Documents", # Old Cal DocRoot value
+                "/Library/CalendarServer/Data", # Old Cal DataRoot value
+                "/Library/AddressBookServer/Documents", # Old AB DocRoot value
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Snow -> Lion Migration, all in default locations, non-/ target",
+            ("/Volumes/old", "/Volumes/new"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/Library/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/old/Library/CalendarServer/Data/" : True,
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+            },
+            (
+                None, # Old ServerRoot value
+                "/Library/CalendarServer/Documents", # Old Cal DocRoot value
+                "/Library/CalendarServer/Data", # Old Cal DataRoot value
+                "/Library/AddressBookServer/Documents", # Old AB DocRoot value
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Snow -> Lion Migration, not in default locations",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/NonStandard/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/NonStandard/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/NonStandard/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/NonStandard/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/NonStandard/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/old/NonStandard/CalendarServer/Data/" : True,
+                "/Volumes/old/NonStandard/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/NonStandard/AddressBookServer/Data/" : True,
+            },
+            (
+                None, # Old ServerRoot value
+                "/NonStandard/CalendarServer/Documents", # Old Cal DocRoot Value
+                "/NonStandard/CalendarServer/Data", # Old Cal DataRoot Value
+                "/NonStandard/AddressBookServer/Documents", # Old AB DocRoot Value
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Snow -> Lion Migration, in internal/external locations",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Volumes/External/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Volumes/External/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/External/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/External/CalendarServer/Data/" : True,
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+            },
+            (
+                None, # Old ServerRoot value
+                "/Volumes/External/CalendarServer/Documents", # Old Cal DocRoot Value
+                "/Volumes/External/CalendarServer/Data", # Old Cal DataRoot Value
+                "/Library/AddressBookServer/Documents", # Old AB DocRoot Value
+                93, 93, # user id, group id
+            )
+        ),
+
+
+        (
+            "Snow -> Lion Migration, only AddressBook data",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+            },
+            (
+                None, # Old ServerRoot value
+                None, # Old Cal DocRoot value
+                None, # Old Cal DataRoot value
+                "/Library/AddressBookServer/Documents", # Old AB DocRoot value
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Lion -> Lion Migration, all in default locations",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/Library/Server/Calendar and Contacts/Documents/" : True,
+                "/Volumes/old/Library/Server/Calendar and Contacts/Data/" : True,
+            },
+            (
+                "/Library/Server/Calendar and Contacts", # Old ServerRoot value
+                "Documents", # Old Cal DocRoot value
+                "Data", # Old Cal DataRoot value
+                None, # Old AB Docs
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Lion -> Lion Migration, not in default locations",
+            ("/Volumes/old", "/"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/NonStandard/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>/Volumes/External/Calendar/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Volumes/External/Calendar/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/NonStandard/Calendar and Contacts/Documents/" : True,
+                "/Volumes/old/NonStandard/Calendar and Contacts/Data/" : True,
+            },
+            (
+                "/NonStandard/Calendar and Contacts", # Old ServerRoot value
+                "/Volumes/External/Calendar/Documents", # Old Cal DocRoot value
+                "/Volumes/External/Calendar/Data", # Old Cal DataRoot value
+                None, # Old AB Docs
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Lion -> Lion Migration, non-/ targetRoot",
+            ("/Volumes/old", "/Volumes/new"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/Library/Server/Calendar and Contacts/Documents/" : True,
+                "/Volumes/old/Library/Server/Calendar and Contacts/Data/" : True,
+            },
+            (
+                "/Library/Server/Calendar and Contacts", # Old ServerRoot value
+                "Documents", # Old Cal DocRoot value
+                "Data", # Old Cal DocRoot value
+                None, # Old AB Docs
+                93, 93, # user id, group id
+            )
+        ),
+
+        (
+            "Lion -> Lion Migration, external ServerRoot with absolute external DocumentRoot and internal DataRoot",
+            ("/Volumes/old", "/Volumes/new"),
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Volumes/External/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>/Volumes/External/CalendarDocuments/</string>
+                        <key>DataRoot</key>
+                        <string>/CalendarData</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/External/Library/Server/Calendar and Contacts/" : True,
+                "/Volumes/External/CalendarDocuments/" : True,
+                "/Volumes/old/CalendarData" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts/" : True,
+            },
+            (
+                "/Volumes/External/Server/Calendar and Contacts", # Old ServerRoot value
+                "/Volumes/External/CalendarDocuments/", # Old Cal DocRoot value
+                "/CalendarData", # Old Cal DocRoot value
+                None, # Old AB Docs
+                93, 93, # user id, group id
+            )
+        ),
+
+
+
+        (
+            "Empty migration, nothing exists",
+            ("/Volumes/old", "/Volumes/new"),
+            {
+            },
+            (
+                None, # Old ServerRoot value
+                None, # Old Cal DocRoot value
+                None, # Old Cal DocRoot value
+                None, # Old AB Docs
+                -1, -1, # user id, group id
+            )
+        ),
+
+
+        ]
+
+        for description, (source, target), paths, expected in info:
+            # print "-=-=-=- %s -=-=-=-" % (description,)
+            accessor = StubDiskAccessor(paths)
+            actual = examinePreviousSystem(source, target, diskAccessor=accessor)
+            self.assertEquals(expected, actual)
+
+
+    def test_relocateData(self):
+
+        info = [
+
+        (
+            "Snow -> Lion Migration, all in default locations",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/Library/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/old/Library/CalendarServer/Data/" : True,
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                None, # oldServerRootValue
+                "/Library/CalendarServer/Documents", # oldCalDocumentRootValue
+                "/Library/CalendarServer/Data", # oldCalDataRootValue
+                "/Library/AddressBookServer/Documents", # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/new/Library/Server/Calendar and Contacts",
+                "/Library/Server/Calendar and Contacts",
+                "Documents",
+                "Data"
+            ),
+            [   # expected DiskAccessor history
+                ('ditto', '/Volumes/old/Library/CalendarServer/Documents', '/Volumes/new/Library/Server/Calendar and Contacts/Documents'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', 93, 93),
+                ('ditto', '/Volumes/old/Library/CalendarServer/Data', '/Volumes/new/Library/Server/Calendar and Contacts/Data'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', 93, 93),
+                ('ditto', '/Volumes/old/Library/AddressBookServer/Documents/addressbooks', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks', 93, 93),
+                ('chown', '/Volumes/new/Library/Server/Calendar and Contacts', 93, 93),
+            ]
+        ),
+
+        (
+            "Snow -> Lion Migration, in non-standard locations",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/NonStandard/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/NonStandard/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/NonStandard/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/NonStandard/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/NonStandard/CalendarServer/Documents/calendars/" : True,
+                "/Volumes/old/NonStandard/CalendarServer/Data/" : True,
+                "/Volumes/old/NonStandard/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/NonStandard/AddressBookServer/Data/" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                None, # oldServerRootValue
+                "/NonStandard/CalendarServer/Documents", # oldCalDocumentRootValue
+                "/NonStandard/CalendarServer/Data", # oldCalDataRootValue
+                "/NonStandard/AddressBookServer/Documents", # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/new/Library/Server/Calendar and Contacts",
+                "/Library/Server/Calendar and Contacts",
+                "Documents",
+                "Data"
+            ),
+            [
+                ('ditto', '/Volumes/old/NonStandard/CalendarServer/Documents', '/Volumes/new/Library/Server/Calendar and Contacts/Documents'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', 93, 93),
+                ('ditto', '/Volumes/old/NonStandard/CalendarServer/Data', '/Volumes/new/Library/Server/Calendar and Contacts/Data'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', 93, 93),
+                ('ditto', '/Volumes/old/NonStandard/AddressBookServer/Documents/addressbooks', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks', 93, 93),
+                ('chown', '/Volumes/new/Library/Server/Calendar and Contacts', 93, 93),
+            ]
+        ),
+
+        (
+            "Snow -> Lion Migration, internal AB, external Cal",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Volumes/External/CalendarServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Volumes/External/CalendarServer/Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/old/private/etc/carddavd/carddavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>DocumentRoot</key>
+                        <string>/Library/AddressBookServer/Documents</string>
+                        <key>DataRoot</key>
+                        <string>/Library/AddressBookServer/Data</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/External/CalendarServer/Documents" : True,
+                "/Volumes/External/CalendarServer/Data" : True,
+                "/Volumes/old/Library/AddressBookServer/Documents/addressbooks/" : True,
+                "/Volumes/old/Library/AddressBookServer/Data/" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                None, # oldServerRootValue
+                "/Volumes/External/CalendarServer/Documents", # oldCalDocumentRootValue
+                "/Volumes/External/CalendarServer/Data", # oldCalDataRootValue
+                "/Library/AddressBookServer/Documents", # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/new/Library/Server/Calendar and Contacts",
+                "/Library/Server/Calendar and Contacts",
+                "/Volumes/External/CalendarServer/Documents",
+                "/Volumes/External/CalendarServer/Data"
+            ),
+            [
+                ('ditto', '/Volumes/old/Library/AddressBookServer/Documents/addressbooks', '/Volumes/External/CalendarServer/Documents/addressbooks'),
+                ('chown-recursive', '/Volumes/External/CalendarServer/Documents/addressbooks', 93, 93),
+                ('chown', '/Volumes/new/Library/Server/Calendar and Contacts', 93, 93),
+            ]
+        ),
+
+        (
+            "Lion -> Lion Migration, all in default locations",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/old/Library/Server/Calendar and Contacts/Documents/" : True,
+                "/Volumes/old/Library/Server/Calendar and Contacts/Data/" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts/" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                "/Library/Server/Calendar and Contacts", # oldServerRootValue
+                "Documents", # oldCalDocumentRootValue
+                "Data", # oldCalDataRootValue
+                None, # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/new/Library/Server/Calendar and Contacts",
+                "/Library/Server/Calendar and Contacts",
+                "Documents",
+                "Data"
+            ),
+            [
+                ('ditto', '/Volumes/old/Library/Server/Calendar and Contacts/Documents', '/Volumes/new/Library/Server/Calendar and Contacts/Documents'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', 93, 93),
+                ('ditto', '/Volumes/old/Library/Server/Calendar and Contacts/Data', '/Volumes/new/Library/Server/Calendar and Contacts/Data'),
+                ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', 93, 93),
+                ('chown', '/Volumes/new/Library/Server/Calendar and Contacts', 93, 93),
+            ]
+        ),
+
+        (
+            "Lion -> Lion Migration, external ServerRoot with relative DocumentRoot and DataRoot",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Volumes/External/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/External/Library/Server/Calendar and Contacts/Documents/" : True,
+                "/Volumes/External/Library/Server/Calendar and Contacts/Data/" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts/" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                "/Volumes/External/Library/Server/Calendar and Contacts", # oldServerRootValue
+                "Documents", # oldCalDocumentRootValue
+                "Data", # oldCalDataRootValue
+                None, # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/External/Library/Server/Calendar and Contacts",
+                "/Volumes/External/Library/Server/Calendar and Contacts",
+                "Documents",
+                "Data"
+            ),
+            [
+                ('chown', '/Volumes/External/Library/Server/Calendar and Contacts', 93, 93),
+            ]
+        ),
+
+
+        (
+            "Lion -> Lion Migration, external ServerRoot with absolute external DocumentRoot and internal DataRoot",
+            {
+                "/Volumes/old/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Volumes/External/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>/Volumes/External/CalendarDocuments/</string>
+                        <key>DataRoot</key>
+                        <string>/CalendarData</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+                "/Volumes/new/private/etc/caldavd/caldavd.plist" : """
+                    <plist version="1.0">
+                    <dict>
+                        <key>ServerRoot</key>
+                        <string>/Library/Server/Calendar and Contacts</string>
+                        <key>DocumentRoot</key>
+                        <string>Documents</string>
+                        <key>DataRoot</key>
+                        <string>Data</string>
+                        <key>UserName</key>
+                        <string>calendar</string>
+                        <key>GroupName</key>
+                        <string>calendar</string>
+                    </dict>
+                    </plist>
+                """,
+
+                "/Volumes/External/Library/Server/Calendar and Contacts/" : True,
+                "/Volumes/External/CalendarDocuments/" : True,
+                "/Volumes/old/CalendarData" : True,
+                "/Volumes/new/Library/Server/Calendar and Contacts/" : True,
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                "/Volumes/External/Library/Server/Calendar and Contacts", # oldServerRootValue
+                "/Volumes/External/CalendarDocuments/", # oldCalDocumentRootValue
+                "/CalendarData", # oldCalDataRootValue
+                None, # oldABDocumentRootValue
+                93, 93, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/External/Library/Server/Calendar and Contacts",
+                "/Volumes/External/Library/Server/Calendar and Contacts",
+                "/Volumes/External/CalendarDocuments",
+                "Data" # Note that DataRoot was copied over to external volume
+            ),
+            [
+                ('ditto', '/Volumes/old/CalendarData', '/Volumes/External/Library/Server/Calendar and Contacts/Data'),
+                ('chown-recursive', '/Volumes/External/Library/Server/Calendar and Contacts/Data', 93, 93),
+                ('chown', '/Volumes/External/Library/Server/Calendar and Contacts', 93, 93),
+            ]
+        ),
+
+        (
+            "Empty migration",
+            {   # no files
+            },
+            (   # args
+                "/Volumes/old", # sourceRoot
+                "/Volumes/new", # targetRoot
+                None, # oldServerRootValue
+                None, # oldCalDocumentRootValue
+                None, # oldCalDataRootValue
+                None, # oldABDocumentRootValue
+                -1, -1, # user id, group id
+            ),
+            (   # expected return values
+                "/Volumes/new/Library/Server/Calendar and Contacts",
+                "/Library/Server/Calendar and Contacts",
+                "Documents",
+                "Data"
+            ),
+            [   # no history
+            ]
+        ),
+
+        ]
+
+        for description, paths, args, expected, history in info:
+            # print "-=-=-=- %s -=-=-=-" % (description,)
+            accessor = StubDiskAccessor(paths)
+            actual = relocateData(*args, diskAccessor=accessor)
+            self.assertEquals(expected, actual)
+            self.assertEquals(history, accessor.history)
+
+
+    def test_stubDiskAccessor(self):
+
+        paths = {
+            "/a/b/c/d" : "foo",
+            "/a/b/c/e" : "bar",
+            "/x/y/z/" : True,
+        }
+        accessor = StubDiskAccessor(paths)
+
+        shouldExist = ["/a", "/a/", "/a/b", "/a/b/", "/a/b/c/d", "/x/y/z"]
+        shouldNotExist = ["/b", "/x/y/z/Z"]
+
+        for path in shouldExist:
+            self.assertTrue(accessor.exists(path))
+        for path in shouldNotExist:
+            self.assertFalse(accessor.exists(path))
+
+        for key, value in paths.iteritems():
+            if value is not True:
+                self.assertEquals(accessor.readFile(key), value)
+
+
+    def test_relativize(self):
+        """
+        Make sure child paths are made relative to their parent
+        """
+        info = [
+            (("/abc/", "/abc/def"), ("/abc", "def")),
+            (("/abc", "/abc/def"), ("/abc", "def")),
+            (("/abc", "/def"), ("/abc", "/def")),
+        ]
+        for args, expected in info:
+            self.assertEquals(expected, relativize(*args))
+
+
+class StubDiskAccessor(object):
+    """
+    A stub which allows testing without actually having real files
+    """
+
+    def __init__(self, paths):
+        self.paths = paths
+        self._fillInDirectories()
+
+        self.reset()
+
+    def _fillInDirectories(self):
+        for key in self.paths.keys():
+            parts = key.split("/")
+            for i in xrange(len(parts)):
+                path = "/".join(parts[:i])
+                self.paths[path] = True
+
+    def addPath(self, path, value):
+        self.paths[path] = value
+        self._fillInDirectories()
+
+    def reset(self):
+        self.history = []
+
+    def exists(self, path):
+        return self.paths.has_key(path.rstrip("/"))
+
+    def readFile(self, path):
+        return self.paths[path]
+
+    def mkdir(self, path):
+        self.history.append(("mkdir", path))
+        self.addPath(path, True)
+
+    def rename(self, before, after):
+        self.history.append(("rename", before, after))
+
+    def isfile(self, path):
+        # FIXME: probably want a better way to denote a directory than "True"
+        return self.exists(path) and self.paths[path] is not True
+
+    def symlink(self, orig, link):
+        self.history.append(("symlink", orig, link))
+
+    def chown(self, path, uid, gid, recursive=False):
+        self.history.append(("chown-recursive" if recursive else "chown", path, uid, gid))
+
+    def walk(self, path, followlinks=True):
+        yield [], [], []
+
+    def listdir(self, path):
+        return []
+
+    def ditto(self, src, dest):
+        self.history.append(("ditto", src, dest))
+        self.addPath(dest, True)
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110408/0f66da39/attachment-0001.html>


More information about the calendarserver-changes mailing list