<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[14519] CalendarServer/branches/users/cdaboo/pod2pod-migration</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/14519">14519</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-03-06 12:13:58 -0800 (Fri, 06 Mar 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Detailed migration logging. Command line tool to drive migration.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtwistedcaldavstdconfigpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/twistedcaldav/stdconfig.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationhome_syncpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_home_syncpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_migrationpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_migration.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationcalendarservertoolspod_migrationpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/pod_migration.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationcalendarservertoolspod_migrationpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/pod_migration.py (0 => 14519)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/pod_migration.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/pod_migration.py        2015-03-06 20:13:58 UTC (rev 14519)
</span><span class="lines">@@ -0,0 +1,293 @@
</span><ins>+#!/usr/bin/env python
+# -*- test-case-name: calendarserver.tools.test.test_calverify -*-
+##
+# Copyright (c) 2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+from __future__ import print_function
+
+&quot;&quot;&quot;
+This tool manages an overall pod migration. Migration is done in a series of steps,
+with the system admin triggering each step individually by running this tool.
+&quot;&quot;&quot;
+
+import os
+import sys
+
+from twisted.internet.defer import inlineCallbacks
+from twisted.python.text import wordWrap
+from twisted.python.usage import Options, UsageError
+
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+from twistedcaldav.timezones import TimezoneCache
+
+from txdav.common.datastore.podding.migration.home_sync import CrossPodHomeSync
+
+from twext.python.log import Logger
+from twext.who.idirectory import RecordType
+
+from calendarserver.tools.cmdline import utilityMain, WorkerService
+
+
+log = Logger()
+
+VERSION = &quot;1&quot;
+
+
+
+def usage(e=None):
+    if e:
+        print(e)
+        print(&quot;&quot;)
+    try:
+        PodMigrationOptions().opt_help()
+    except SystemExit:
+        pass
+    if e:
+        sys.exit(64)
+    else:
+        sys.exit(0)
+
+
+description = ''.join(
+    wordWrap(
+        &quot;&quot;&quot;
+        Usage: calendarserver_pod_migration [options] [input specifiers]
+        &quot;&quot;&quot;,
+        int(os.environ.get('COLUMNS', '80'))
+    )
+)
+description += &quot;\nVersion: %s&quot; % (VERSION,)
+
+
+
+class ConfigError(Exception):
+    pass
+
+
+
+class PodMigrationOptions(Options):
+    &quot;&quot;&quot;
+    Command-line options for 'calendarserver_pod_migration'
+    &quot;&quot;&quot;
+
+    synopsis = description
+
+    optFlags = [
+        ['verbose', 'v', &quot;Verbose logging.&quot;],
+        ['debug', 'D', &quot;Debug logging.&quot;],
+        ['step1', '1', &quot;Run step 1 of the migration (initial sync)&quot;],
+        ['step2', '2', &quot;Run step 2 of the migration (incremental sync)&quot;],
+        ['step3', '3', &quot;Run step 3 of the migration (prepare for final sync)&quot;],
+        ['step4', '4', &quot;Run step 4 of the migration (final incremental sync)&quot;],
+        ['step5', '5', &quot;Run step 5 of the migration (final reconcile sync)&quot;],
+        ['step6', '6', &quot;Run step 6 of the migration (enable new home)&quot;],
+        ['step7', '7', &quot;Run step 7 of the migration (remove old home)&quot;],
+    ]
+
+    optParameters = [
+        ['config', 'f', DEFAULT_CONFIG_FILE, &quot;Specify caldavd.plist configuration path.&quot;],
+        ['uid', 'u', &quot;&quot;, &quot;Directory record uid of user to migrate [REQUIRED]&quot;],
+    ]
+
+    longdesc = &quot;Only one step option is allowed.&quot;
+
+    def __init__(self):
+        super(PodMigrationOptions, self).__init__()
+        self.outputName = '-'
+
+
+    def opt_output(self, filename):
+        &quot;&quot;&quot;
+        Specify output file path (default: '-', meaning stdout).
+        &quot;&quot;&quot;
+        self.outputName = filename
+
+    opt_o = opt_output
+
+
+    def openOutput(self):
+        &quot;&quot;&quot;
+        Open the appropriate output file based on the '--output' option.
+        &quot;&quot;&quot;
+        if self.outputName == '-':
+            return sys.stdout
+        else:
+            return open(self.outputName, 'wb')
+
+
+    def postOptions(self):
+        runstep = None
+        for step in range(7):
+            if self[&quot;step{}&quot;.format(step + 1)]:
+                if runstep is None:
+                    runstep = step
+                    self[&quot;runstep&quot;] = step + 1
+                else:
+                    raise UsageError(&quot;Only one step option allowed&quot;)
+        else:
+            if runstep is None:
+                raise UsageError(&quot;One step option must be present&quot;)
+        if not self[&quot;uid&quot;]:
+            raise UsageError(&quot;A uid is required&quot;)
+
+
+
+class PodMigrationService(WorkerService, object):
+    &quot;&quot;&quot;
+    Service which runs, does its stuff, then stops the reactor.
+    &quot;&quot;&quot;
+
+    def __init__(self, store, options, output, reactor, config):
+        super(PodMigrationService, self).__init__(store)
+        self.options = options
+        self.output = output
+        self.reactor = reactor
+        self.config = config
+        TimezoneCache.create()
+
+
+    @inlineCallbacks
+    def doWork(self):
+        &quot;&quot;&quot;
+        Do the work, stopping the reactor when done.
+        &quot;&quot;&quot;
+        self.output.write(&quot;\n---- Pod Migration version: %s ----\n&quot; % (VERSION,))
+
+        # Map short name to uid
+        record = yield self.store.directoryService().recordWithUID(self.options[&quot;uid&quot;])
+        if record is None:
+            record = yield self.store.directoryService().recordWithShortName(RecordType.user, self.options[&quot;uid&quot;])
+            if record is not None:
+                self.options[&quot;uid&quot;] = record.uid
+
+        try:
+            yield getattr(self, &quot;step{}&quot;.format(self.options[&quot;runstep&quot;]))()
+            self.output.close()
+        except ConfigError:
+            pass
+        except:
+            log.failure(&quot;doWork()&quot;)
+
+
+    @inlineCallbacks
+    def step1(self):
+        syncer = CrossPodHomeSync(
+            self.store,
+            self.options[&quot;uid&quot;],
+            uselog=self.output if self.options[&quot;verbose&quot;] else None
+        )
+        syncer.accounting(&quot;Pod Migration Step 1\n&quot;)
+        yield syncer.sync()
+
+
+    @inlineCallbacks
+    def step2(self):
+        syncer = CrossPodHomeSync(
+            self.store,
+            self.options[&quot;uid&quot;],
+            uselog=self.output if self.options[&quot;verbose&quot;] else None
+        )
+        syncer.accounting(&quot;Pod Migration Step 2\n&quot;)
+        yield syncer.sync()
+
+
+    @inlineCallbacks
+    def step3(self):
+        syncer = CrossPodHomeSync(
+            self.store,
+            self.options[&quot;uid&quot;],
+            uselog=self.output if self.options[&quot;verbose&quot;] else None
+        )
+        syncer.accounting(&quot;Pod Migration Step 3\n&quot;)
+        yield syncer.disableRemoteHome()
+
+
+    @inlineCallbacks
+    def step4(self):
+        syncer = CrossPodHomeSync(
+            self.store,
+            self.options[&quot;uid&quot;],
+            final=True,
+            uselog=self.output if self.options[&quot;verbose&quot;] else None
+        )
+        syncer.accounting(&quot;Pod Migration Step 4\n&quot;)
+        yield syncer.sync()
+
+
+    @inlineCallbacks
+    def step5(self):
+        syncer = CrossPodHomeSync(
+            self.store,
+            self.options[&quot;uid&quot;],
+            final=True,
+            uselog=self.output if self.options[&quot;verbose&quot;] else None
+        )
+        syncer.accounting(&quot;Pod Migration Step 5\n&quot;)
+        yield syncer.finalSync()
+
+
+    @inlineCallbacks
+    def step6(self):
+        syncer = CrossPodHomeSync(
+            self.store,
+            self.options[&quot;uid&quot;],
+            uselog=self.output if self.options[&quot;verbose&quot;] else None
+        )
+        syncer.accounting(&quot;Pod Migration Step 6\n&quot;)
+        yield syncer.enableLocalHome()
+
+
+    @inlineCallbacks
+    def step7(self):
+        syncer = CrossPodHomeSync(
+            self.store,
+            self.options[&quot;uid&quot;],
+            final=True,
+            uselog=self.output if self.options[&quot;verbose&quot;] else None
+        )
+        syncer.accounting(&quot;Pod Migration Step 7\n&quot;)
+        yield syncer.removeRemoteHome()
+
+
+
+def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
+    &quot;&quot;&quot;
+    Do the export.
+    &quot;&quot;&quot;
+    if reactor is None:
+        from twisted.internet import reactor
+    options = PodMigrationOptions()
+    try:
+        options.parseOptions(argv[1:])
+    except UsageError as e:
+        stderr.write(&quot;Invalid options specified\n&quot;)
+        options.opt_help()
+
+    try:
+        output = options.openOutput()
+    except IOError, e:
+        stderr.write(&quot;Unable to open output file for writing: %s\n&quot; % (e))
+        sys.exit(1)
+
+
+    def makeService(store):
+        from twistedcaldav.config import config
+        config.TransactionTimeoutSeconds = 0
+        return PodMigrationService(store, options, output, reactor, config)
+
+    utilityMain(options['config'], makeService, reactor, verbose=options[&quot;debug&quot;])
+
+if __name__ == '__main__':
+    main()
</ins><span class="cx">Property changes on: CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/pod_migration.py
</span><span class="cx">___________________________________________________________________
</span></span></pre></div>
<a id="svnexecutable"></a>
<div class="addfile"><h4>Added: svn:executable</h4></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtwistedcaldavstdconfigpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/twistedcaldav/stdconfig.py (14518 => 14519)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/twistedcaldav/stdconfig.py        2015-03-06 19:34:30 UTC (rev 14518)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/twistedcaldav/stdconfig.py        2015-03-06 20:13:58 UTC (rev 14519)
</span><span class="lines">@@ -405,6 +405,7 @@
</span><span class="cx">         &quot;Implicit Errors&quot;: False,
</span><span class="cx">         &quot;AutoScheduling&quot;: False,
</span><span class="cx">         &quot;iSchedule&quot;: False,
</span><ins>+        &quot;migration&quot;: False,
</ins><span class="cx">     },
</span><span class="cx">     &quot;AccountingPrincipals&quot;: [],
</span><span class="cx">     &quot;AccountingLogRoot&quot;   : &quot;accounting&quot;,
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationhome_syncpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py (14518 => 14519)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py        2015-03-06 19:34:30 UTC (rev 14518)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py        2015-03-06 20:13:58 UTC (rev 14519)
</span><span class="lines">@@ -19,6 +19,7 @@
</span><span class="cx"> from twext.python.log import Logger
</span><span class="cx"> from twisted.internet.defer import returnValue, inlineCallbacks
</span><span class="cx"> from twisted.python.failure import Failure
</span><ins>+from twistedcaldav.accounting import emitAccounting
</ins><span class="cx"> from txdav.caldav.icalendarstore import ComponentUpdateState
</span><span class="cx"> from txdav.common.datastore.podding.migration.sync_metadata import CalendarMigrationRecord, \
</span><span class="cx">     CalendarObjectMigrationRecord, AttachmentMigrationRecord
</span><span class="lines">@@ -30,9 +31,12 @@
</span><span class="cx"> from txdav.common.idirectoryservice import DirectoryRecordNotFoundError
</span><span class="cx"> 
</span><span class="cx"> from uuid import uuid4
</span><ins>+import datetime
</ins><span class="cx"> 
</span><span class="cx"> log = Logger()
</span><span class="cx"> 
</span><ins>+ACCOUNTING_TYPE = &quot;migration&quot;
+ACCOUNTING_LOG = &quot;migration.log&quot;
</ins><span class="cx"> 
</span><span class="cx"> def inTransactionWrapper(operation):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="lines">@@ -85,7 +89,7 @@
</span><span class="cx"> 
</span><span class="cx">     BATCH_SIZE = 50
</span><span class="cx"> 
</span><del>-    def __init__(self, store, diruid, final=False):
</del><ins>+    def __init__(self, store, diruid, final=False, uselog=None):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @param store: the data store
</span><span class="cx">         @type store: L{CommonDataStore}
</span><span class="lines">@@ -94,11 +98,14 @@
</span><span class="cx">         @param final: indicates whether this is in the final sync stage with the remote home
</span><span class="cx">             already disabled
</span><span class="cx">         @type final: L{bool}
</span><ins>+        @param uselog: additional logging written to this object
+        @type: L{File}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         self.store = store
</span><span class="cx">         self.diruid = diruid
</span><span class="cx">         self.disabledRemote = final
</span><ins>+        self.uselog = uselog
</ins><span class="cx">         self.record = None
</span><span class="cx">         self.homeId = None
</span><span class="cx"> 
</span><span class="lines">@@ -107,6 +114,12 @@
</span><span class="cx">         return &quot;Cross-pod Migration Sync for {}: {}&quot;.format(self.diruid, detail)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def accounting(self, logstr):
+        emitAccounting(ACCOUNTING_TYPE, self.record, &quot;{} {}\n&quot;.format(datetime.datetime.now().isoformat(), logstr), filename=ACCOUNTING_LOG)
+        if self.uselog is not None:
+            self.uselog.write(&quot;CrossPodHomeSync: {}\n&quot;.format(logstr))
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def migrateHere(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -154,6 +167,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         yield self.loadRecord()
</span><ins>+        self.accounting(&quot;Starting: sync...&quot;)
</ins><span class="cx">         yield self.prepareCalendarHome()
</span><span class="cx"> 
</span><span class="cx">         # Calendar list and calendar data
</span><span class="lines">@@ -165,7 +179,9 @@
</span><span class="cx">         # Sync attachments
</span><span class="cx">         yield self.syncAttachments()
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: sync.\n&quot;)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def finalSync(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -174,6 +190,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         yield self.loadRecord()
</span><ins>+        self.accounting(&quot;Starting: finalSync...&quot;)
</ins><span class="cx">         yield self.prepareCalendarHome()
</span><span class="cx"> 
</span><span class="cx">         # Link attachments to resources: ATTACHMENT_CALENDAR_OBJECT table
</span><span class="lines">@@ -198,7 +215,9 @@
</span><span class="cx">         # TODO: work items
</span><span class="cx">         pass
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: finalSync.\n&quot;)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inTransactionWrapper
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def disableRemoteHome(self, txn):
</span><span class="lines">@@ -207,6 +226,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         yield self.loadRecord()
</span><ins>+        self.accounting(&quot;Starting: disableRemoteHome...&quot;)
</ins><span class="cx">         yield self.prepareCalendarHome()
</span><span class="cx"> 
</span><span class="cx">         # Calendar home
</span><span class="lines">@@ -219,7 +239,9 @@
</span><span class="cx"> 
</span><span class="cx">         self.disabledRemote = True
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: disableRemoteHome.\n&quot;)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inTransactionWrapper
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def enableLocalHome(self, txn):
</span><span class="lines">@@ -228,6 +250,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         yield self.loadRecord()
</span><ins>+        self.accounting(&quot;Starting: enableLocalHome...&quot;)
</ins><span class="cx">         yield self.prepareCalendarHome()
</span><span class="cx"> 
</span><span class="cx">         # Disable any local external homes
</span><span class="lines">@@ -249,7 +272,9 @@
</span><span class="cx">         # TODO: purge the old ones
</span><span class="cx">         pass
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: enableLocalHome.\n&quot;)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def removeRemoteHome(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -259,9 +284,12 @@
</span><span class="cx">         # TODO: implement API on CommonHome to purge the old data without
</span><span class="cx">         # any side-effects (scheduling, sharing etc).
</span><span class="cx">         yield self.loadRecord()
</span><ins>+        self.accounting(&quot;Starting: removeRemoteHome...&quot;)
</ins><span class="cx">         yield self.prepareCalendarHome()
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: removeRemoteHome.\n&quot;)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def loadRecord(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -290,6 +318,7 @@
</span><span class="cx">                     self.homeId = None
</span><span class="cx">                 else:
</span><span class="cx">                     home = yield txn.calendarHomeWithUID(self.diruid, status=_HOME_STATUS_MIGRATING, create=True)
</span><ins>+                    self.accounting(&quot;  Created new home collection to migrate into.&quot;)
</ins><span class="cx">             self.homeId = home.id() if home is not None else None
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -300,6 +329,7 @@
</span><span class="cx">         Make sure the home meta-data (alarms, default calendars) is properly sync'd
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Starting: syncCalendarHomeMetaData...&quot;)
</ins><span class="cx">         remote_home = yield self._remoteHome(txn)
</span><span class="cx">         yield remote_home.readMetaData()
</span><span class="cx"> 
</span><span class="lines">@@ -309,7 +339,9 @@
</span><span class="cx">         local_home = yield self._localHome(txn)
</span><span class="cx">         yield local_home.copyMetadata(remote_home, calendarIDMap)
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: syncCalendarHomeMetaData.&quot;)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _remoteHome(self, txn):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -350,11 +382,15 @@
</span><span class="cx">         Synchronize each owned calendar.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Starting: syncCalendarList...&quot;)
+
</ins><span class="cx">         # Remote sync details
</span><span class="cx">         remote_sync_state = yield self.getCalendarSyncList()
</span><ins>+        self.accounting(&quot;  Found {} remote calendars to sync.&quot;.format(len(remote_sync_state)))
</ins><span class="cx"> 
</span><span class="cx">         # Get local sync details from local DB
</span><span class="cx">         local_sync_state = yield self.getSyncState()
</span><ins>+        self.accounting(&quot;  Found {} local calendars to sync.&quot;.format(len(local_sync_state)))
</ins><span class="cx"> 
</span><span class="cx">         # Remove local calendars no longer on the remote side
</span><span class="cx">         yield self.purgeLocal(local_sync_state, remote_sync_state)
</span><span class="lines">@@ -363,7 +399,9 @@
</span><span class="cx">         for remoteID in remote_sync_state.keys():
</span><span class="cx">             yield self.syncCalendar(remoteID, local_sync_state, remote_sync_state)
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: syncCalendarList.&quot;)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inTransactionWrapper
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def getCalendarSyncList(self, txn):
</span><span class="lines">@@ -433,11 +471,12 @@
</span><span class="cx">         @type remote_sync_state: L{dict}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         home = yield self._localHome(txn)
</span><del>-        for remoteID in set(local_sync_state.keys()) - set(remote_sync_state.keys()):
-            calendar = yield home.childWithID(local_sync_state[remoteID].localResourceID)
</del><ins>+        for localID in set(local_sync_state.keys()) - set(remote_sync_state.keys()):
+            calendar = yield home.childWithID(local_sync_state[localID].localResourceID)
</ins><span class="cx">             if calendar is not None:
</span><span class="cx">                 yield calendar.purge()
</span><del>-            del local_sync_state[remoteID]
</del><ins>+            del local_sync_state[localID]
+            self.accounting(&quot;  Purged calendar local-id={} that no longer exists on the remote pod.&quot;.format(localID))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -454,6 +493,8 @@
</span><span class="cx">         @type remote_sync_state: L{dict}
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Starting: syncCalendar.&quot;)
+
</ins><span class="cx">         # See if we need to create the local one first
</span><span class="cx">         if remoteID not in local_sync_state:
</span><span class="cx">             localID = yield self.newCalendar()
</span><span class="lines">@@ -463,6 +504,10 @@
</span><span class="cx">                 localResourceID=localID,
</span><span class="cx">                 lastSyncToken=None,
</span><span class="cx">             )
</span><ins>+            self.accounting(&quot;  Created new calendar local-id={}, remote-id={}.&quot;.format(localID, remoteID))
+        else:
+            localID = local_sync_state.get(remoteID).localResourceID
+            self.accounting(&quot;  Updating calendar local-id={}, remote-id={}.&quot;.format(localID, remoteID))
</ins><span class="cx">         local_record = local_sync_state.get(remoteID)
</span><span class="cx"> 
</span><span class="cx">         remote_token = remote_sync_state[remoteID].lastSyncToken
</span><span class="lines">@@ -471,11 +516,13 @@
</span><span class="cx">             yield self.syncCalendarMetaData(local_record)
</span><span class="cx"> 
</span><span class="cx">             # Sync object resources
</span><del>-            changed, deleted = yield self.findObjectsToSync(local_record)
-            yield self.purgeDeletedObjectsInBatches(local_record, deleted)
</del><ins>+            changed, removed = yield self.findObjectsToSync(local_record)
+            self.accounting(&quot;  Calendar objects changed={}, removed={}.&quot;.format(len(changed), len(removed)))
+            yield self.purgeDeletedObjectsInBatches(local_record, removed)
</ins><span class="cx">             yield self.updateChangedObjectsInBatches(local_record, changed)
</span><span class="cx"> 
</span><span class="cx">         yield self.updateSyncState(local_record, remote_token)
</span><ins>+        self.accounting(&quot;Completed: syncCalendar.&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inTransactionWrapper
</span><span class="lines">@@ -500,6 +547,7 @@
</span><span class="cx">         @param migrationRecord: current migration record
</span><span class="cx">         @type localID: L{CalendarMigrationRecord}
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
</ins><span class="cx">         # Remote changes
</span><span class="cx">         remote_home = yield self._remoteHome(txn)
</span><span class="cx">         remote_calendar = yield remote_home.childWithID(migrationRecord.remoteResourceID)
</span><span class="lines">@@ -510,6 +558,7 @@
</span><span class="cx">         local_home = yield self._localHome(txn)
</span><span class="cx">         local_calendar = yield local_home.childWithID(migrationRecord.localResourceID)
</span><span class="cx">         yield local_calendar.copyMetadata(remote_calendar)
</span><ins>+        self.accounting(&quot;  Copied calendar meta-data for calendar local-id={0.localResourceID}, remote-id={0.remoteResourceID}.&quot;.format(migrationRecord))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inTransactionWrapper
</span><span class="lines">@@ -592,6 +641,7 @@
</span><span class="cx"> 
</span><span class="cx">         for local_object in local_objects:
</span><span class="cx">             yield local_object.purge()
</span><ins>+            self.accounting(&quot;  Purged calendar object local-id={}.&quot;.format(local_object.id()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -660,6 +710,7 @@
</span><span class="cx">                 local_object = yield local_objects[obj_name]
</span><span class="cx">                 yield local_object._setComponentInternal(remote_data, internal_state=ComponentUpdateState.RAW)
</span><span class="cx">                 del local_objects[obj_name]
</span><ins>+                log_op = &quot;Updated&quot;
</ins><span class="cx">             else:
</span><span class="cx">                 local_object = yield local_calendar._createCalendarObjectWithNameInternal(obj_name, remote_data, internal_state=ComponentUpdateState.RAW)
</span><span class="cx"> 
</span><span class="lines">@@ -671,13 +722,16 @@
</span><span class="cx">                     remoteResourceID=remote_object.id(),
</span><span class="cx">                     localResourceID=local_object.id()
</span><span class="cx">                 )
</span><ins>+                log_op = &quot;Created&quot;
</ins><span class="cx"> 
</span><span class="cx">             # Sync meta-data such as schedule object, schedule tags, access mode etc
</span><span class="cx">             yield local_object.copyMetadata(remote_object)
</span><ins>+            self.accounting(&quot;  {} calendar object local-id={}, remote-id={}.&quot;.format(log_op, local_object.id(), remote_object.id()))
</ins><span class="cx"> 
</span><span class="cx">         # Purge the ones that remain
</span><span class="cx">         for local_object in local_objects.values():
</span><span class="cx">             yield local_object.purge()
</span><ins>+            self.accounting(&quot;  Purged calendar object local-id={}.&quot;.format(local_object.id()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -686,12 +740,17 @@
</span><span class="cx">         Sync attachments (both metadata and actual attachment data) for the home being migrated.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Starting: syncAttachments...&quot;)
+
</ins><span class="cx">         # Two steps - sync the table first in one txn, then sync each attachment's data
</span><span class="cx">         changed_ids, removed_ids = yield self.syncAttachmentTable()
</span><ins>+        self.accounting(&quot;  Attachments changed={}, removed={}&quot;.format(len(changed_ids), len(removed_ids)))
</ins><span class="cx"> 
</span><span class="cx">         for local_id in changed_ids:
</span><span class="cx">             yield self.syncAttachmentData(local_id)
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: syncAttachments.&quot;)
+
</ins><span class="cx">         returnValue((changed_ids, removed_ids,))
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -772,6 +831,7 @@
</span><span class="cx">         if records:
</span><span class="cx">             # Read the data from the conduit
</span><span class="cx">             yield remote_home.readAttachmentData(records[0].remoteResourceID, attachment)
</span><ins>+            self.accounting(&quot;  Read attachment local-id={0.localResourceID}, remote-id={0.remoteResourceID}&quot;.format(records[0]))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -780,8 +840,11 @@
</span><span class="cx">         Link attachments to the calendar objects they belong to.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Starting: linkAttachments...&quot;)
+
</ins><span class="cx">         # Get the map of links for the remote home
</span><span class="cx">         links = yield self.getAttachmentLinks()
</span><ins>+        self.accounting(&quot;  Linking {} attachments&quot;.format(len(links)))
</ins><span class="cx"> 
</span><span class="cx">         # Get remote-&gt;local ID mappings
</span><span class="cx">         attachmentIDMap, objectIDMap = yield self.getAttachmentMappings()
</span><span class="lines">@@ -792,6 +855,8 @@
</span><span class="cx">             yield self.makeAttachmentLinks(links[:50], attachmentIDMap, objectIDMap)
</span><span class="cx">             links = links[50:]
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: linkAttachments.&quot;)
+
</ins><span class="cx">         returnValue(len_links)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -854,11 +919,15 @@
</span><span class="cx">         a fake directory UID locally.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Starting: delegateReconcile...&quot;)
+
</ins><span class="cx">         yield self.individualDelegateReconcile()
</span><span class="cx">         yield self.groupDelegateReconcile()
</span><span class="cx">         yield self.externalDelegateReconcile()
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: delegateReconcile.&quot;)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inTransactionWrapper
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def individualDelegateReconcile(self, txn):
</span><span class="lines">@@ -870,7 +939,9 @@
</span><span class="cx">         for record in remote_records:
</span><span class="cx">             yield record.insert(txn)
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;  Found {} individual delegates&quot;.format(len(remote_records)))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inTransactionWrapper
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def groupDelegateReconcile(self, txn):
</span><span class="lines">@@ -885,7 +956,9 @@
</span><span class="cx">             delegator.groupID = local_group.groupID
</span><span class="cx">             yield delegator.insert(txn)
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;  Found {} group delegates&quot;.format(len(remote_records)))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inTransactionWrapper
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def externalDelegateReconcile(self, txn):
</span><span class="lines">@@ -897,15 +970,20 @@
</span><span class="cx">         for record in remote_records:
</span><span class="cx">             yield record.insert(txn)
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;  Found {} external delegates&quot;.format(len(remote_records)))
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def groupAttendeeReconcile(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Sync the remote group attendee links to the local store.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Starting: groupAttendeeReconcile...&quot;)
+
</ins><span class="cx">         # Get remote data and local mapping information
</span><span class="cx">         remote_group_attendees, objectIDMap = yield self.groupAttendeeData()
</span><ins>+        self.accounting(&quot;  Found {} group attendees&quot;.format(len(remote_group_attendees)))
</ins><span class="cx"> 
</span><span class="cx">         # Map each result to a local resource (in batches)
</span><span class="cx">         number_of_links = len(remote_group_attendees)
</span><span class="lines">@@ -913,6 +991,8 @@
</span><span class="cx">             yield self.groupAttendeeProcess(remote_group_attendees[:50], objectIDMap)
</span><span class="cx">             remote_group_attendees = remote_group_attendees[50:]
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: groupAttendeeReconcile.&quot;)
+
</ins><span class="cx">         returnValue(number_of_links)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -957,7 +1037,9 @@
</span><span class="cx">         Sync all the existing L{NotificationObject} resources from the remote store.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Starting: notificationsReconcile...&quot;)
</ins><span class="cx">         records = yield self.notificationRecords()
</span><ins>+        self.accounting(&quot;  Found {} notifications&quot;.format(len(records)))
</ins><span class="cx"> 
</span><span class="cx">         # Batch setting resources for the local home
</span><span class="cx">         len_records = len(records)
</span><span class="lines">@@ -965,6 +1047,8 @@
</span><span class="cx">             yield self.makeNotifications(records[:50])
</span><span class="cx">             records = records[50:]
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: notificationsReconcile.&quot;)
+
</ins><span class="cx">         returnValue(len_records)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -998,7 +1082,8 @@
</span><span class="cx">         for record in records:
</span><span class="cx">             # Do this via the &quot;write&quot; API so that sync revisions are updated properly, rather than just
</span><span class="cx">             # inserting the records directly.
</span><del>-            yield notifications.writeNotificationObject(record.notificationUID, record.notificationType, record.notificationData)
</del><ins>+            notification = yield notifications.writeNotificationObject(record.notificationUID, record.notificationType, record.notificationData)
+            self.accounting(&quot;  Added notification local-id={}.&quot;.format(notification.id()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -1019,6 +1104,7 @@
</span><span class="cx">         A -&gt; C        |  B -&gt; C (new)                | (removed)
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Starting: sharedByCollectionsReconcile...&quot;)
</ins><span class="cx">         calendars = yield self.getSyncState()
</span><span class="cx"> 
</span><span class="cx">         len_records = 0
</span><span class="lines">@@ -1028,6 +1114,10 @@
</span><span class="cx">                 continue
</span><span class="cx">             records = records.items()
</span><span class="cx"> 
</span><ins>+            self.accounting(&quot;  Found shared by calendar local-id={0.localResourceID}, remote-id={0.remoteResourceID} with {1} sharees&quot;.format(
+                calendar, len(records),
+            ))
+
</ins><span class="cx">             # Batch setting resources for the local home
</span><span class="cx">             len_records += len(records)
</span><span class="cx">             while records:
</span><span class="lines">@@ -1040,6 +1130,8 @@
</span><span class="cx">             # Update the remote pod to switch over the shares
</span><span class="cx">             yield self.updatedRemoteSharedByCollections(calendar.remoteResourceID, bindUID)
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: sharedByCollectionsReconcile.&quot;)
+
</ins><span class="cx">         returnValue(len_records)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1093,12 +1185,14 @@
</span><span class="cx">                     calendarResourceID=calendar_id,
</span><span class="cx">                     bindRevision=0,
</span><span class="cx">                 )
</span><ins>+                self.accounting(&quot;    Updating existing sharee {}&quot;.format(shareeHome.uid()))
</ins><span class="cx">             else:
</span><span class="cx">                 # Map the record resource ids and insert a new record
</span><span class="cx">                 record.calendarHomeResourceID = shareeHome.id()
</span><span class="cx">                 record.calendarResourceID = calendar_id
</span><span class="cx">                 record.bindRevision = 0
</span><span class="cx">                 yield record.insert(txn)
</span><ins>+                self.accounting(&quot;    Adding new sharee {}&quot;.format(shareeHome.uid()))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inTransactionWrapper
</span><span class="lines">@@ -1116,6 +1210,7 @@
</span><span class="cx">             share.groupID = local_group.groupID
</span><span class="cx">             share.calendarID = local_id
</span><span class="cx">             yield share.insert(txn)
</span><ins>+            self.accounting(&quot;    Adding group sharee {}&quot;.format(local_group.groupUID))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inTransactionWrapper
</span><span class="lines">@@ -1128,6 +1223,7 @@
</span><span class="cx">         remote_home = yield self._remoteHome(txn)
</span><span class="cx">         remote_calendar = yield remote_home.childWithID(remote_id)
</span><span class="cx">         records = yield remote_calendar.migrateBindRecords(bindUID)
</span><ins>+        self.accounting(&quot;    Updating remote records&quot;)
</ins><span class="cx">         returnValue(records)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1147,14 +1243,20 @@
</span><span class="cx">         B -&gt; A        |  B -&gt; B (modify existing)    | (removed)
</span><span class="cx">         C -&gt; A        |  C -&gt; B (new)                | (removed)
</span><span class="cx">         &quot;&quot;&quot;
</span><ins>+
+        self.accounting(&quot;Starting: sharedToCollectionsReconcile...&quot;)
+
</ins><span class="cx">         records = yield self.sharedToCollectionRecords()
</span><span class="cx">         records = records.items()
</span><span class="cx">         len_records = len(records)
</span><ins>+        self.accounting(&quot;  Found {} shared to collections&quot;.format(len_records))
</ins><span class="cx"> 
</span><span class="cx">         while records:
</span><span class="cx">             yield self.makeSharedToCollections(records[:50])
</span><span class="cx">             records = records[50:]
</span><span class="cx"> 
</span><ins>+        self.accounting(&quot;Completed: sharedToCollectionsReconcile.&quot;)
+
</ins><span class="cx">         returnValue(len_records)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -1198,6 +1300,7 @@
</span><span class="cx">                     yield oldrecord[0].update(
</span><span class="cx">                         calendarHomeResourceID=self.homeId,
</span><span class="cx">                     )
</span><ins>+                    self.accounting(&quot;  Updated existing local sharer record {}&quot;.format(sharerHome.uid()))
</ins><span class="cx">                 else:
</span><span class="cx">                     raise AssertionError(&quot;An existing share must be present&quot;)
</span><span class="cx">             else:
</span><span class="lines">@@ -1217,6 +1320,7 @@
</span><span class="cx">                 if oldrecord:
</span><span class="cx">                     # Map the record resource ids and insert a new record
</span><span class="cx">                     calendar_id = oldrecord.calendarResourceID
</span><ins>+                    log_op = &quot;Updated&quot;
</ins><span class="cx">                 else:
</span><span class="cx">                     sharerView = yield sharerHome.createCollectionForExternalShare(
</span><span class="cx">                         ownerRecord.calendarResourceName,
</span><span class="lines">@@ -1224,11 +1328,13 @@
</span><span class="cx">                         metadataRecord.supportedComponents,
</span><span class="cx">                     )
</span><span class="cx">                     calendar_id = sharerView.id()
</span><ins>+                    log_op = &quot;Created&quot;
</ins><span class="cx"> 
</span><span class="cx">                 shareeRecord.calendarHomeResourceID = self.homeId
</span><span class="cx">                 shareeRecord.calendarResourceID = calendar_id
</span><span class="cx">                 shareeRecord.bindRevision = 0
</span><span class="cx">                 yield shareeRecord.insert(txn)
</span><ins>+                self.accounting(&quot;  {} remote sharer record {}&quot;.format(log_op, sharerHome.uid()))
</ins><span class="cx"> 
</span><span class="cx">                 yield self.updatedRemoteSharedToCollection(remote_id, txn=txn)
</span><span class="cx"> 
</span><span class="lines">@@ -1243,4 +1349,5 @@
</span><span class="cx">         remote_home = yield self._remoteHome(txn)
</span><span class="cx">         remote_calendar = yield remote_home.childWithID(remote_id)
</span><span class="cx">         records = yield remote_calendar.migrateBindRecords(None)
</span><ins>+        self.accounting(&quot;    Updating remote records&quot;)
</ins><span class="cx">         returnValue(records)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_home_syncpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py (14518 => 14519)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py        2015-03-06 19:34:30 UTC (rev 14518)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py        2015-03-06 20:13:58 UTC (rev 14519)
</span><span class="lines">@@ -32,7 +32,8 @@
</span><span class="cx">     ExternalDelegateGroupsRecord, DelegateGroupsRecord, GroupsRecord
</span><span class="cx"> from txdav.common.datastore.sql_notification import NotificationCollection
</span><span class="cx"> from txdav.common.datastore.sql_tables import schema, _HOME_STATUS_EXTERNAL, \
</span><del>-    _BIND_MODE_READ, _HOME_STATUS_MIGRATING
</del><ins>+    _BIND_MODE_READ, _HOME_STATUS_MIGRATING, _HOME_STATUS_NORMAL, \
+    _HOME_STATUS_DISABLED
</ins><span class="cx"> from txdav.common.datastore.test.util import populateCalendarsFrom
</span><span class="cx"> from txdav.who.delegates import Delegates
</span><span class="cx"> from txweb2.http_headers import MimeType
</span><span class="lines">@@ -981,7 +982,35 @@
</span><span class="cx">         yield self.commitTransaction(1)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def test_disable_remote_home(self):
+        &quot;&quot;&quot;
+        Test that L{disableRemoteHome} changes the remote status and prevents a normal state
+        home from being created.
+        &quot;&quot;&quot;
</ins><span class="cx"> 
</span><ins>+        # Create remote home - and add some fake notifications
+        yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
+        yield self.theTransactionUnderTest(0).notificationsWithUID(&quot;user01&quot;, create=True)
+        yield self.commitTransaction(0)
+
+        # Sync from remote side
+        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), &quot;user01&quot;)
+        yield syncer.loadRecord()
+        yield syncer.prepareCalendarHome()
+        yield syncer.disableRemoteHome()
+
+        # It is disabled
+        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;)
+        self.assertTrue(home is None)
+        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, status=_HOME_STATUS_NORMAL)
+        self.assertTrue(home is None)
+        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, status=_HOME_STATUS_DISABLED)
+        self.assertTrue(home is not None)
+        yield self.commitTransaction(0)
+
+
+
</ins><span class="cx"> class TestSharingSync(MultiStoreConduitTest):
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Test that L{CrossPodHomeSync} sharing sync works.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_migrationpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_migration.py (14518 => 14519)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_migration.py        2015-03-06 19:34:30 UTC (rev 14518)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_migration.py        2015-03-06 20:13:58 UTC (rev 14519)
</span><span class="lines">@@ -72,6 +72,8 @@
</span><span class="cx">         config.GroupAttendees.Enabled = True
</span><span class="cx">         config.GroupAttendees.ReconciliationDelaySeconds = 0
</span><span class="cx">         config.GroupAttendees.AutoUpdateSecondsFromNow = 0
</span><ins>+        config.AccountingCategories.migration = True
+        config.AccountingPrincipals = [&quot;*&quot;]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span></span></pre>
</div>
</div>

</body>
</html>