[CalendarServer-changes] [14156] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Wed Nov 12 10:09:15 PST 2014


Revision: 14156
          http://trac.calendarserver.org//changeset/14156
Author:   cdaboo at apple.com
Date:     2014-11-12 10:09:15 -0800 (Wed, 12 Nov 2014)
Log Message:
-----------
Fix cross-pod group proxy change handling.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/caldav.py
    CalendarServer/trunk/calendarserver/tools/util.py
    CalendarServer/trunk/conf/auth/accounts-test-pod.xml
    CalendarServer/trunk/requirements-dev.txt
    CalendarServer/trunk/requirements-stable.txt
    CalendarServer/trunk/twistedcaldav/cache.py
    CalendarServer/trunk/twistedcaldav/controlapi.py
    CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/trunk/txdav/common/datastore/podding/directory.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/dps/client.py
    CalendarServer/trunk/txdav/dps/commands.py
    CalendarServer/trunk/txdav/dps/server.py
    CalendarServer/trunk/txdav/who/augment.py
    CalendarServer/trunk/txdav/who/cache.py
    CalendarServer/trunk/txdav/who/groups.py
    CalendarServer/trunk/txdav/who/test/test_delegates.py
    CalendarServer/trunk/txdav/who/test/test_groups.py

Modified: CalendarServer/trunk/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/caldav.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/calendarserver/tap/caldav.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -94,6 +94,7 @@
 from txdav.who.groups import GroupCacher
 
 from twistedcaldav import memcachepool
+from twistedcaldav.cache import MemcacheURLPatternChangeNotifier
 from twistedcaldav.config import ConfigurationError
 from twistedcaldav.localization import processLocalizationFiles
 from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
@@ -916,10 +917,12 @@
 
         # Optionally set up group cacher
         if config.GroupCaching.Enabled:
+            cacheNotifier = MemcacheURLPatternChangeNotifier("/principals/__uids__/{token}/", cacheHandle="PrincipalToken") if config.EnableResponseCache else None
             groupCacher = GroupCacher(
                 directory,
                 updateSeconds=config.GroupCaching.UpdateSeconds,
                 useDirectoryBasedDelegates=config.GroupCaching.UseDirectoryBasedDelegates,
+                cacheNotifier=cacheNotifier,
             )
         else:
             groupCacher = None
@@ -1285,10 +1288,12 @@
 
             # Optionally set up group cacher
             if config.GroupCaching.Enabled:
+                cacheNotifier = MemcacheURLPatternChangeNotifier("/principals/__uids__/{token}/", cacheHandle="PrincipalToken") if config.EnableResponseCache else None
                 groupCacher = GroupCacher(
                     directory,
                     updateSeconds=config.GroupCaching.UpdateSeconds,
                     useDirectoryBasedDelegates=config.GroupCaching.UseDirectoryBasedDelegates,
+                    cacheNotifier=cacheNotifier,
                 )
             else:
                 groupCacher = None
@@ -1872,10 +1877,12 @@
 
             # Optionally set up group cacher
             if config.GroupCaching.Enabled:
+                cacheNotifier = MemcacheURLPatternChangeNotifier("/principals/__uids__/{token}/", cacheHandle="PrincipalToken") if config.EnableResponseCache else None
                 groupCacher = GroupCacher(
                     directory,
                     updateSeconds=config.GroupCaching.UpdateSeconds,
                     useDirectoryBasedDelegates=config.GroupCaching.UseDirectoryBasedDelegates,
+                    cacheNotifier=cacheNotifier,
                 )
             else:
                 groupCacher = None

Modified: CalendarServer/trunk/calendarserver/tools/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/util.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/calendarserver/tools/util.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -293,6 +293,8 @@
 
         recordType, shortName = principalID.split(":", 1)
         recordType = directory.oldNameToRecordType(recordType)
+        if recordType is None:
+            returnValue(None)
 
         returnValue((yield directory.recordWithShortName(recordType, shortName)))
 

Modified: CalendarServer/trunk/conf/auth/accounts-test-pod.xml
===================================================================
--- CalendarServer/trunk/conf/auth/accounts-test-pod.xml	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/conf/auth/accounts-test-pod.xml	2014-11-12 18:09:15 UTC (rev 14156)
@@ -1627,4 +1627,718 @@
     <full-name>Puser 100</full-name>
     <email>puser100 at example.com</email>
 </record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000001</uid>
+    <guid>20000000-0000-0000-0000-000000000001</guid>
+    <short-name>group01</short-name>
+    <full-name>Group 01</full-name>
+    <email>group01 at example.com</email>
+    <member-uid>10000000-0000-0000-0000-000000000001</member-uid>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000002</uid>
+    <guid>20000000-0000-0000-0000-000000000002</guid>
+    <short-name>group02</short-name>
+    <full-name>Group 02</full-name>
+    <email>group02 at example.com</email>
+    <member-uid>10000000-0000-0000-0000-000000000006</member-uid>
+    <member-uid>10000000-0000-0000-0000-000000000007</member-uid>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000003</uid>
+    <guid>20000000-0000-0000-0000-000000000003</guid>
+    <short-name>group03</short-name>
+    <full-name>Group 03</full-name>
+    <email>group03 at example.com</email>
+    <member-uid>10000000-0000-0000-0000-000000000008</member-uid>
+    <member-uid>10000000-0000-0000-0000-000000000009</member-uid>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000004</uid>
+    <guid>20000000-0000-0000-0000-000000000004</guid>
+    <short-name>group04</short-name>
+    <full-name>Group 04</full-name>
+    <email>group04 at example.com</email>
+    <member-uid>20000000-0000-0000-0000-000000000002</member-uid>
+    <member-uid>20000000-0000-0000-0000-000000000003</member-uid>
+    <member-uid>10000000-0000-0000-0000-000000000010</member-uid>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000005</uid>
+    <guid>20000000-0000-0000-0000-000000000005</guid>
+    <short-name>group05</short-name>
+    <full-name>Group 05</full-name>
+    <email>group05 at example.com</email>
+    <member-uid>20000000-0000-0000-0000-000000000006</member-uid>
+    <member-uid>10000000-0000-0000-0000-000000000020</member-uid>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000006</uid>
+    <guid>20000000-0000-0000-0000-000000000006</guid>
+    <short-name>group06</short-name>
+    <full-name>Group 06</full-name>
+    <email>group06 at example.com</email>
+    <member-uid>10000000-0000-0000-0000-000000000021</member-uid>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000007</uid>
+    <guid>20000000-0000-0000-0000-000000000007</guid>
+    <short-name>group07</short-name>
+    <full-name>Group 07</full-name>
+    <email>group07 at example.com</email>
+    <member-uid>10000000-0000-0000-0000-000000000022</member-uid>
+    <member-uid>10000000-0000-0000-0000-000000000023</member-uid>
+    <member-uid>10000000-0000-0000-0000-000000000024</member-uid>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000008</uid>
+    <guid>20000000-0000-0000-0000-000000000008</guid>
+    <short-name>group08</short-name>
+    <full-name>Group 08</full-name>
+    <email>group08 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000009</uid>
+    <guid>20000000-0000-0000-0000-000000000009</guid>
+    <short-name>group09</short-name>
+    <full-name>Group 09</full-name>
+    <email>group09 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000010</uid>
+    <guid>20000000-0000-0000-0000-000000000010</guid>
+    <short-name>group10</short-name>
+    <full-name>Group 10</full-name>
+    <email>group10 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000011</uid>
+    <guid>20000000-0000-0000-0000-000000000011</guid>
+    <short-name>group11</short-name>
+    <full-name>Group 11</full-name>
+    <email>group11 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000012</uid>
+    <guid>20000000-0000-0000-0000-000000000012</guid>
+    <short-name>group12</short-name>
+    <full-name>Group 12</full-name>
+    <email>group12 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000013</uid>
+    <guid>20000000-0000-0000-0000-000000000013</guid>
+    <short-name>group13</short-name>
+    <full-name>Group 13</full-name>
+    <email>group13 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000014</uid>
+    <guid>20000000-0000-0000-0000-000000000014</guid>
+    <short-name>group14</short-name>
+    <full-name>Group 14</full-name>
+    <email>group14 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000015</uid>
+    <guid>20000000-0000-0000-0000-000000000015</guid>
+    <short-name>group15</short-name>
+    <full-name>Group 15</full-name>
+    <email>group15 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000016</uid>
+    <guid>20000000-0000-0000-0000-000000000016</guid>
+    <short-name>group16</short-name>
+    <full-name>Group 16</full-name>
+    <email>group16 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000017</uid>
+    <guid>20000000-0000-0000-0000-000000000017</guid>
+    <short-name>group17</short-name>
+    <full-name>Group 17</full-name>
+    <email>group17 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000018</uid>
+    <guid>20000000-0000-0000-0000-000000000018</guid>
+    <short-name>group18</short-name>
+    <full-name>Group 18</full-name>
+    <email>group18 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000019</uid>
+    <guid>20000000-0000-0000-0000-000000000019</guid>
+    <short-name>group19</short-name>
+    <full-name>Group 19</full-name>
+    <email>group19 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000020</uid>
+    <guid>20000000-0000-0000-0000-000000000020</guid>
+    <short-name>group20</short-name>
+    <full-name>Group 20</full-name>
+    <email>group20 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000021</uid>
+    <guid>20000000-0000-0000-0000-000000000021</guid>
+    <short-name>group21</short-name>
+    <full-name>Group 21</full-name>
+    <email>group21 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000022</uid>
+    <guid>20000000-0000-0000-0000-000000000022</guid>
+    <short-name>group22</short-name>
+    <full-name>Group 22</full-name>
+    <email>group22 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000023</uid>
+    <guid>20000000-0000-0000-0000-000000000023</guid>
+    <short-name>group23</short-name>
+    <full-name>Group 23</full-name>
+    <email>group23 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000024</uid>
+    <guid>20000000-0000-0000-0000-000000000024</guid>
+    <short-name>group24</short-name>
+    <full-name>Group 24</full-name>
+    <email>group24 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000025</uid>
+    <guid>20000000-0000-0000-0000-000000000025</guid>
+    <short-name>group25</short-name>
+    <full-name>Group 25</full-name>
+    <email>group25 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000026</uid>
+    <guid>20000000-0000-0000-0000-000000000026</guid>
+    <short-name>group26</short-name>
+    <full-name>Group 26</full-name>
+    <email>group26 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000027</uid>
+    <guid>20000000-0000-0000-0000-000000000027</guid>
+    <short-name>group27</short-name>
+    <full-name>Group 27</full-name>
+    <email>group27 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000028</uid>
+    <guid>20000000-0000-0000-0000-000000000028</guid>
+    <short-name>group28</short-name>
+    <full-name>Group 28</full-name>
+    <email>group28 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000029</uid>
+    <guid>20000000-0000-0000-0000-000000000029</guid>
+    <short-name>group29</short-name>
+    <full-name>Group 29</full-name>
+    <email>group29 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000030</uid>
+    <guid>20000000-0000-0000-0000-000000000030</guid>
+    <short-name>group30</short-name>
+    <full-name>Group 30</full-name>
+    <email>group30 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000031</uid>
+    <guid>20000000-0000-0000-0000-000000000031</guid>
+    <short-name>group31</short-name>
+    <full-name>Group 31</full-name>
+    <email>group31 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000032</uid>
+    <guid>20000000-0000-0000-0000-000000000032</guid>
+    <short-name>group32</short-name>
+    <full-name>Group 32</full-name>
+    <email>group32 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000033</uid>
+    <guid>20000000-0000-0000-0000-000000000033</guid>
+    <short-name>group33</short-name>
+    <full-name>Group 33</full-name>
+    <email>group33 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000034</uid>
+    <guid>20000000-0000-0000-0000-000000000034</guid>
+    <short-name>group34</short-name>
+    <full-name>Group 34</full-name>
+    <email>group34 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000035</uid>
+    <guid>20000000-0000-0000-0000-000000000035</guid>
+    <short-name>group35</short-name>
+    <full-name>Group 35</full-name>
+    <email>group35 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000036</uid>
+    <guid>20000000-0000-0000-0000-000000000036</guid>
+    <short-name>group36</short-name>
+    <full-name>Group 36</full-name>
+    <email>group36 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000037</uid>
+    <guid>20000000-0000-0000-0000-000000000037</guid>
+    <short-name>group37</short-name>
+    <full-name>Group 37</full-name>
+    <email>group37 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000038</uid>
+    <guid>20000000-0000-0000-0000-000000000038</guid>
+    <short-name>group38</short-name>
+    <full-name>Group 38</full-name>
+    <email>group38 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000039</uid>
+    <guid>20000000-0000-0000-0000-000000000039</guid>
+    <short-name>group39</short-name>
+    <full-name>Group 39</full-name>
+    <email>group39 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000040</uid>
+    <guid>20000000-0000-0000-0000-000000000040</guid>
+    <short-name>group40</short-name>
+    <full-name>Group 40</full-name>
+    <email>group40 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000041</uid>
+    <guid>20000000-0000-0000-0000-000000000041</guid>
+    <short-name>group41</short-name>
+    <full-name>Group 41</full-name>
+    <email>group41 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000042</uid>
+    <guid>20000000-0000-0000-0000-000000000042</guid>
+    <short-name>group42</short-name>
+    <full-name>Group 42</full-name>
+    <email>group42 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000043</uid>
+    <guid>20000000-0000-0000-0000-000000000043</guid>
+    <short-name>group43</short-name>
+    <full-name>Group 43</full-name>
+    <email>group43 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000044</uid>
+    <guid>20000000-0000-0000-0000-000000000044</guid>
+    <short-name>group44</short-name>
+    <full-name>Group 44</full-name>
+    <email>group44 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000045</uid>
+    <guid>20000000-0000-0000-0000-000000000045</guid>
+    <short-name>group45</short-name>
+    <full-name>Group 45</full-name>
+    <email>group45 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000046</uid>
+    <guid>20000000-0000-0000-0000-000000000046</guid>
+    <short-name>group46</short-name>
+    <full-name>Group 46</full-name>
+    <email>group46 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000047</uid>
+    <guid>20000000-0000-0000-0000-000000000047</guid>
+    <short-name>group47</short-name>
+    <full-name>Group 47</full-name>
+    <email>group47 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000048</uid>
+    <guid>20000000-0000-0000-0000-000000000048</guid>
+    <short-name>group48</short-name>
+    <full-name>Group 48</full-name>
+    <email>group48 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000049</uid>
+    <guid>20000000-0000-0000-0000-000000000049</guid>
+    <short-name>group49</short-name>
+    <full-name>Group 49</full-name>
+    <email>group49 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000050</uid>
+    <guid>20000000-0000-0000-0000-000000000050</guid>
+    <short-name>group50</short-name>
+    <full-name>Group 50</full-name>
+    <email>group50 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000051</uid>
+    <guid>20000000-0000-0000-0000-000000000051</guid>
+    <short-name>group51</short-name>
+    <full-name>Group 51</full-name>
+    <email>group51 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000052</uid>
+    <guid>20000000-0000-0000-0000-000000000052</guid>
+    <short-name>group52</short-name>
+    <full-name>Group 52</full-name>
+    <email>group52 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000053</uid>
+    <guid>20000000-0000-0000-0000-000000000053</guid>
+    <short-name>group53</short-name>
+    <full-name>Group 53</full-name>
+    <email>group53 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000054</uid>
+    <guid>20000000-0000-0000-0000-000000000054</guid>
+    <short-name>group54</short-name>
+    <full-name>Group 54</full-name>
+    <email>group54 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000055</uid>
+    <guid>20000000-0000-0000-0000-000000000055</guid>
+    <short-name>group55</short-name>
+    <full-name>Group 55</full-name>
+    <email>group55 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000056</uid>
+    <guid>20000000-0000-0000-0000-000000000056</guid>
+    <short-name>group56</short-name>
+    <full-name>Group 56</full-name>
+    <email>group56 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000057</uid>
+    <guid>20000000-0000-0000-0000-000000000057</guid>
+    <short-name>group57</short-name>
+    <full-name>Group 57</full-name>
+    <email>group57 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000058</uid>
+    <guid>20000000-0000-0000-0000-000000000058</guid>
+    <short-name>group58</short-name>
+    <full-name>Group 58</full-name>
+    <email>group58 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000059</uid>
+    <guid>20000000-0000-0000-0000-000000000059</guid>
+    <short-name>group59</short-name>
+    <full-name>Group 59</full-name>
+    <email>group59 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000060</uid>
+    <guid>20000000-0000-0000-0000-000000000060</guid>
+    <short-name>group60</short-name>
+    <full-name>Group 60</full-name>
+    <email>group60 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000061</uid>
+    <guid>20000000-0000-0000-0000-000000000061</guid>
+    <short-name>group61</short-name>
+    <full-name>Group 61</full-name>
+    <email>group61 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000062</uid>
+    <guid>20000000-0000-0000-0000-000000000062</guid>
+    <short-name>group62</short-name>
+    <full-name>Group 62</full-name>
+    <email>group62 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000063</uid>
+    <guid>20000000-0000-0000-0000-000000000063</guid>
+    <short-name>group63</short-name>
+    <full-name>Group 63</full-name>
+    <email>group63 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000064</uid>
+    <guid>20000000-0000-0000-0000-000000000064</guid>
+    <short-name>group64</short-name>
+    <full-name>Group 64</full-name>
+    <email>group64 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000065</uid>
+    <guid>20000000-0000-0000-0000-000000000065</guid>
+    <short-name>group65</short-name>
+    <full-name>Group 65</full-name>
+    <email>group65 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000066</uid>
+    <guid>20000000-0000-0000-0000-000000000066</guid>
+    <short-name>group66</short-name>
+    <full-name>Group 66</full-name>
+    <email>group66 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000067</uid>
+    <guid>20000000-0000-0000-0000-000000000067</guid>
+    <short-name>group67</short-name>
+    <full-name>Group 67</full-name>
+    <email>group67 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000068</uid>
+    <guid>20000000-0000-0000-0000-000000000068</guid>
+    <short-name>group68</short-name>
+    <full-name>Group 68</full-name>
+    <email>group68 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000069</uid>
+    <guid>20000000-0000-0000-0000-000000000069</guid>
+    <short-name>group69</short-name>
+    <full-name>Group 69</full-name>
+    <email>group69 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000070</uid>
+    <guid>20000000-0000-0000-0000-000000000070</guid>
+    <short-name>group70</short-name>
+    <full-name>Group 70</full-name>
+    <email>group70 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000071</uid>
+    <guid>20000000-0000-0000-0000-000000000071</guid>
+    <short-name>group71</short-name>
+    <full-name>Group 71</full-name>
+    <email>group71 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000072</uid>
+    <guid>20000000-0000-0000-0000-000000000072</guid>
+    <short-name>group72</short-name>
+    <full-name>Group 72</full-name>
+    <email>group72 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000073</uid>
+    <guid>20000000-0000-0000-0000-000000000073</guid>
+    <short-name>group73</short-name>
+    <full-name>Group 73</full-name>
+    <email>group73 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000074</uid>
+    <guid>20000000-0000-0000-0000-000000000074</guid>
+    <short-name>group74</short-name>
+    <full-name>Group 74</full-name>
+    <email>group74 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000075</uid>
+    <guid>20000000-0000-0000-0000-000000000075</guid>
+    <short-name>group75</short-name>
+    <full-name>Group 75</full-name>
+    <email>group75 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000076</uid>
+    <guid>20000000-0000-0000-0000-000000000076</guid>
+    <short-name>group76</short-name>
+    <full-name>Group 76</full-name>
+    <email>group76 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000077</uid>
+    <guid>20000000-0000-0000-0000-000000000077</guid>
+    <short-name>group77</short-name>
+    <full-name>Group 77</full-name>
+    <email>group77 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000078</uid>
+    <guid>20000000-0000-0000-0000-000000000078</guid>
+    <short-name>group78</short-name>
+    <full-name>Group 78</full-name>
+    <email>group78 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000079</uid>
+    <guid>20000000-0000-0000-0000-000000000079</guid>
+    <short-name>group79</short-name>
+    <full-name>Group 79</full-name>
+    <email>group79 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000080</uid>
+    <guid>20000000-0000-0000-0000-000000000080</guid>
+    <short-name>group80</short-name>
+    <full-name>Group 80</full-name>
+    <email>group80 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000081</uid>
+    <guid>20000000-0000-0000-0000-000000000081</guid>
+    <short-name>group81</short-name>
+    <full-name>Group 81</full-name>
+    <email>group81 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000082</uid>
+    <guid>20000000-0000-0000-0000-000000000082</guid>
+    <short-name>group82</short-name>
+    <full-name>Group 82</full-name>
+    <email>group82 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000083</uid>
+    <guid>20000000-0000-0000-0000-000000000083</guid>
+    <short-name>group83</short-name>
+    <full-name>Group 83</full-name>
+    <email>group83 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000084</uid>
+    <guid>20000000-0000-0000-0000-000000000084</guid>
+    <short-name>group84</short-name>
+    <full-name>Group 84</full-name>
+    <email>group84 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000085</uid>
+    <guid>20000000-0000-0000-0000-000000000085</guid>
+    <short-name>group85</short-name>
+    <full-name>Group 85</full-name>
+    <email>group85 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000086</uid>
+    <guid>20000000-0000-0000-0000-000000000086</guid>
+    <short-name>group86</short-name>
+    <full-name>Group 86</full-name>
+    <email>group86 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000087</uid>
+    <guid>20000000-0000-0000-0000-000000000087</guid>
+    <short-name>group87</short-name>
+    <full-name>Group 87</full-name>
+    <email>group87 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000088</uid>
+    <guid>20000000-0000-0000-0000-000000000088</guid>
+    <short-name>group88</short-name>
+    <full-name>Group 88</full-name>
+    <email>group88 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000089</uid>
+    <guid>20000000-0000-0000-0000-000000000089</guid>
+    <short-name>group89</short-name>
+    <full-name>Group 89</full-name>
+    <email>group89 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000090</uid>
+    <guid>20000000-0000-0000-0000-000000000090</guid>
+    <short-name>group90</short-name>
+    <full-name>Group 90</full-name>
+    <email>group90 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000091</uid>
+    <guid>20000000-0000-0000-0000-000000000091</guid>
+    <short-name>group91</short-name>
+    <full-name>Group 91</full-name>
+    <email>group91 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000092</uid>
+    <guid>20000000-0000-0000-0000-000000000092</guid>
+    <short-name>group92</short-name>
+    <full-name>Group 92</full-name>
+    <email>group92 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000093</uid>
+    <guid>20000000-0000-0000-0000-000000000093</guid>
+    <short-name>group93</short-name>
+    <full-name>Group 93</full-name>
+    <email>group93 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000094</uid>
+    <guid>20000000-0000-0000-0000-000000000094</guid>
+    <short-name>group94</short-name>
+    <full-name>Group 94</full-name>
+    <email>group94 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000095</uid>
+    <guid>20000000-0000-0000-0000-000000000095</guid>
+    <short-name>group95</short-name>
+    <full-name>Group 95</full-name>
+    <email>group95 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000096</uid>
+    <guid>20000000-0000-0000-0000-000000000096</guid>
+    <short-name>group96</short-name>
+    <full-name>Group 96</full-name>
+    <email>group96 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000097</uid>
+    <guid>20000000-0000-0000-0000-000000000097</guid>
+    <short-name>group97</short-name>
+    <full-name>Group 97</full-name>
+    <email>group97 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000098</uid>
+    <guid>20000000-0000-0000-0000-000000000098</guid>
+    <short-name>group98</short-name>
+    <full-name>Group 98</full-name>
+    <email>group98 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000099</uid>
+    <guid>20000000-0000-0000-0000-000000000099</guid>
+    <short-name>group99</short-name>
+    <full-name>Group 99</full-name>
+    <email>group99 at example.com</email>
+</record>
+<record type="group">
+    <uid>20000000-0000-0000-0000-000000000100</uid>
+    <guid>20000000-0000-0000-0000-000000000100</guid>
+    <short-name>group100</short-name>
+    <full-name>Group 100</full-name>
+    <email>group100 at example.com</email>
+</record>
 </directory>

Modified: CalendarServer/trunk/requirements-dev.txt
===================================================================
--- CalendarServer/trunk/requirements-dev.txt	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/requirements-dev.txt	2014-11-12 18:09:15 UTC (rev 14156)
@@ -7,4 +7,4 @@
 mockldap
 q
 --editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVClientLibrary/trunk@13420#egg=CalDAVClientLibrary
---editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@14118#egg=CalDAVTester
+--editable svn+http://svn.calendarserver.org/repository/calendarserver/CalDAVTester/trunk@14154#egg=CalDAVTester

Modified: CalendarServer/trunk/requirements-stable.txt
===================================================================
--- CalendarServer/trunk/requirements-stable.txt	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/requirements-stable.txt	2014-11-12 18:09:15 UTC (rev 14156)
@@ -5,7 +5,7 @@
 # For CalendarServer development, don't try to get these projects from PyPI; use svn.
 
 -e .
--e svn+http://svn.calendarserver.org/repository/calendarserver/twext/trunk@14133#egg=twextpy
+-e svn+http://svn.calendarserver.org/repository/calendarserver/twext/trunk@14155#egg=twextpy
 -e svn+http://svn.calendarserver.org/repository/calendarserver/PyKerberos/trunk@13420#egg=kerberos
 -e svn+http://svn.calendarserver.org/repository/calendarserver/PyCalendar/trunk@14025#egg=pycalendar
 

Modified: CalendarServer/trunk/twistedcaldav/cache.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/cache.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/twistedcaldav/cache.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -135,6 +135,40 @@
 
 
 
+class MemcacheURLPatternChangeNotifier(CachePoolUserMixIn):
+    """
+    A change notifier used to target arbitrary tokens.
+    """
+    log = Logger()
+
+    def __init__(self, urlPattern, cachePool=None, cacheHandle="Default"):
+        self._urlPattern = urlPattern
+        self._cachePool = cachePool
+        self._cachePoolHandle = cacheHandle
+
+
+    def _newCacheToken(self):
+        return str(uuid.uuid4())
+
+
+    def changed(self, token):
+        """
+        Change the cache token for a resource
+
+        return: A L{Deferred} that fires when the token has been changed.
+        """
+
+        url = self._urlPattern.format(token=token)
+
+        self.log.debug("Changing Cache Token for {url}", url=url)
+        return self.getCachePool().set(
+            'cacheToken:{url}'.format(url=url),
+            self._newCacheToken(),
+            expireTime=config.ResponseCacheTimeout * 60,
+        )
+
+
+
 class BaseResponseCache(object):
     """
     A base class which provides some common operations

Modified: CalendarServer/trunk/twistedcaldav/controlapi.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/controlapi.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/twistedcaldav/controlapi.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -325,6 +325,7 @@
     @inlineCallbacks
     def action_refreshgroups(self, j):
         txn = self._store.newTransaction()
+        yield txn.directoryService().flush()
         wp = yield GroupCacherPollingWork.reschedule(txn, 0, force=True)
         jobID = wp.workItem.jobID
         yield txn.commit()

Modified: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -303,14 +303,23 @@
             principals.append(principal)
             newUIDs.add(principal.principalUID())
 
-        # Get the old set of UIDs
-        # oldUIDs = (yield self._index().getMembers(self.uid))
+        # Get the old set of expanded UIDs
+        oldUIDs = set()
         oldPrincipals = yield self.groupMembers()
-        oldUIDs = [p.principalUID() for p in oldPrincipals]
+        oldUIDs.update([p.principalUID() for p in oldPrincipals])
+        oldPrincipals = yield self.expandedGroupMembers()
+        oldUIDs.update([p.principalUID() for p in oldPrincipals])
 
         # Change membership
         yield self.setGroupMemberSetPrincipals(principals)
 
+        # Get the new set of UIDs
+        newUIDs = set()
+        newPrincipals = yield self.groupMembers()
+        newUIDs.update([p.principalUID() for p in newPrincipals])
+        newPrincipals = yield self.expandedGroupMembers()
+        newUIDs.update([p.principalUID() for p in newPrincipals])
+
         # Invalidate the primary principal's cache, and any principal's whose
         # membership status changed
         yield self.parent.cacheNotifier.changed()

Modified: CalendarServer/trunk/txdav/common/datastore/podding/directory.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/podding/directory.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/txdav/common/datastore/podding/directory.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -27,6 +27,55 @@
     """
 
     @inlineCallbacks
+    def send_all_group_delegates(self, txn, server):
+        """
+        Request all group delegates on another pod.
+
+        @param txn: transaction to use
+        @type txn: L{CommonStoreTransaction}
+        @param server: server to query
+        @type server: L{Server}
+        """
+
+        request = {
+            "action": "all-group-delegates",
+        }
+        response = yield self.sendRequestToServer(txn, server, request)
+
+        if response["result"] == "ok":
+            returnValue(set(response["value"]))
+        elif response["result"] == "exception":
+            raise namedClass(response["class"])(response["result"])
+
+
+    @inlineCallbacks
+    def recv_all_group_delegates(self, txn, request):
+        """
+        Process an all group delegates cross-pod request. Request arguments as per L{send_all_group_delegates}.
+
+        @param request: request arguments
+        @type request: C{dict}
+        """
+
+        if request["action"] != "all-group-delegates":
+            raise FailedCrossPodRequestError("Wrong action '{}' for recv_all_group_delegates".format(request["action"]))
+
+        try:
+            delegatedUIDs = yield txn.allGroupDelegates()
+        except Exception as e:
+            returnValue({
+                "result": "exception",
+                "class": ".".join((e.__class__.__module__, e.__class__.__name__,)),
+                "request": str(e),
+            })
+
+        returnValue({
+            "result": "ok",
+            "value": list(delegatedUIDs),
+        })
+
+
+    @inlineCallbacks
     def send_set_delegates(self, txn, delegator, delegates, readWrite):
         """
         Set delegates for delegator on another pod.

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -1376,9 +1376,11 @@
             )
 
         if membershipChanged:
-            yield self.synchronizeMembers(groupID, set(memberUIDs))
+            addedUIDs, removedUIDs = yield self.synchronizeMembers(groupID, set(memberUIDs))
+        else:
+            addedUIDs = removedUIDs = None
 
-        returnValue(membershipChanged)
+        returnValue((membershipChanged, addedUIDs, removedUIDs,))
 
 
     @inlineCallbacks
@@ -1394,22 +1396,19 @@
         @param newMemberUIDs: set of new member UIDs in the group
         @type newMemberUIDs: L{set} of L{str}
         """
-        numRemoved = numAdded = 0
         cachedMemberUIDs = (yield self.groupMemberUIDs(groupID))
 
         removed = cachedMemberUIDs - newMemberUIDs
         for memberUID in removed:
-            numRemoved += 1
             yield self.removeMemberFromGroup(memberUID, groupID)
 
         added = newMemberUIDs - cachedMemberUIDs
         for memberUID in added:
-            numAdded += 1
             yield self.addMemberToGroup(memberUID, groupID)
 
         yield self.groupChanged(groupID, added, removed)
 
-        returnValue((numAdded, numRemoved))
+        returnValue((added, removed,))
 
 
     @inlineCallbacks

Modified: CalendarServer/trunk/txdav/dps/client.py
===================================================================
--- CalendarServer/trunk/txdav/dps/client.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/txdav/dps/client.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -44,7 +44,7 @@
     WikiAccessForUIDCommand, ContinuationCommand,
     StatsCommand, ExternalDelegatesCommand, ExpandedMemberUIDsCommand,
     AddMembersCommand, RemoveMembersCommand,
-    UpdateRecordsCommand, ExpandedMembersCommand
+    UpdateRecordsCommand, ExpandedMembersCommand, FlushCommand
 )
 from txdav.who.delegates import RecordType as DelegatesRecordType
 from txdav.who.directory import (
@@ -423,6 +423,15 @@
 
 
     @inlineCallbacks
+    def flush(self):
+        try:
+            yield self._sendCommand(FlushCommand)
+            returnValue(None)
+        except ConnectError:
+            returnValue(None)
+
+
+    @inlineCallbacks
     def stats(self):
         try:
             result = yield self._sendCommand(StatsCommand)

Modified: CalendarServer/trunk/txdav/dps/commands.py
===================================================================
--- CalendarServer/trunk/txdav/dps/commands.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/txdav/dps/commands.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -266,6 +266,14 @@
 
 
 
+class FlushCommand(amp.Command):
+    arguments = []
+    response = [
+        ('flush', amp.Boolean()),
+    ]
+
+
+
 class StatsCommand(amp.Command):
     arguments = []
     response = [

Modified: CalendarServer/trunk/txdav/dps/server.py
===================================================================
--- CalendarServer/trunk/txdav/dps/server.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/txdav/dps/server.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -41,7 +41,7 @@
     WikiAccessForUIDCommand, ContinuationCommand,
     ExternalDelegatesCommand, StatsCommand, ExpandedMemberUIDsCommand,
     AddMembersCommand, RemoveMembersCommand,
-    UpdateRecordsCommand, # RemoveRecordsCommand,
+    UpdateRecordsCommand, FlushCommand, # RemoveRecordsCommand,
 )
 from txdav.who.wiki import WikiAccessLevel
 from zope.interface import implementer
@@ -647,6 +647,16 @@
         returnValue(response)
 
 
+    @FlushCommand.responder
+    @inlineCallbacks
+    def flush(self):
+        yield self._directory.flush()
+        response = {
+            "flush": True,
+        }
+        returnValue(response)
+
+
     @StatsCommand.responder
     @inlineCallbacks
     def stats(self):

Modified: CalendarServer/trunk/txdav/who/augment.py
===================================================================
--- CalendarServer/trunk/txdav/who/augment.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/txdav/who/augment.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -128,6 +128,10 @@
         cls._timings[key] = (count, timeSpent)
 
 
+    def flush(self):
+        return self._directory.flush()
+
+
     def stats(self):
         results = {}
         results.update(self._timings)

Modified: CalendarServer/trunk/txdav/who/cache.py
===================================================================
--- CalendarServer/trunk/txdav/who/cache.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/txdav/who/cache.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -416,5 +416,11 @@
         )
 
 
+    @inlineCallbacks
+    def flush(self):
+        self.resetCache()
+        yield self._directory.flush()
+
+
     def stats(self):
         return self._directory.stats()

Modified: CalendarServer/trunk/txdav/who/groups.py
===================================================================
--- CalendarServer/trunk/txdav/who/groups.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/txdav/who/groups.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -23,11 +23,13 @@
 from twext.enterprise.dal.syntax import Delete, Select, Parameter
 from twext.enterprise.jobqueue import WorkItem, RegeneratingWorkItem
 from twext.python.log import Logger
-from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed, \
+    DeferredList
 from twistedcaldav.config import config
 from txdav.caldav.datastore.sql import CalendarStoreFeatures
 from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
 import datetime
+import itertools
 import time
 
 log = Logger()
@@ -249,13 +251,15 @@
         self, directory,
         updateSeconds=600,
         useDirectoryBasedDelegates=False,
-        directoryBasedDelegatesSource=None
+        directoryBasedDelegatesSource=None,
+        cacheNotifier=None,
     ):
         self.directory = directory
         self.useDirectoryBasedDelegates = useDirectoryBasedDelegates
         if useDirectoryBasedDelegates and directoryBasedDelegatesSource is None:
             directoryBasedDelegatesSource = self.directory.recordsWithDirectoryBasedDelegates
         self.directoryBasedDelegatesSource = directoryBasedDelegatesSource
+        self.cacheNotifier = cacheNotifier
         self.updateSeconds = updateSeconds
 
 
@@ -440,19 +444,30 @@
         )
 
         if groupID:
-            membershipChanged = yield txn.refreshGroup(
+            membershipChanged, addedUIDs, removedUIDs = yield txn.refreshGroup(
                 groupUID, record, groupID,
                 cachedName, cachedMembershipHash, cachedExtant
             )
 
             if membershipChanged:
                 self.log.info(
-                    "Membership changed for group {uid} {name}",
+                    "Membership changed for group {uid} {name}:\n\tadded {added}\n\tremoved {removed}",
                     uid=groupUID,
-                    name=cachedName
+                    name=cachedName,
+                    added=",".join(addedUIDs),
+                    removed=",".join(removedUIDs),
                 )
+
+                # Send cache change notifications
+                if self.cacheNotifier is not None:
+                    self.cacheNotifier.changed(groupUID)
+                    for uid in itertools.chain(addedUIDs, removedUIDs):
+                        self.cacheNotifier.changed(uid)
+
+                # Notifier other store APIs of changes
                 wpsAttendee = yield self.scheduleGroupAttendeeReconciliations(txn, groupID)
                 wpsShareee = yield self.scheduleGroupShareeReconciliations(txn, groupID)
+
                 returnValue(wpsAttendee + wpsShareee)
             else:
                 self.log.debug(
@@ -536,11 +551,24 @@
 
     @inlineCallbacks
     def groupsToRefresh(self, txn):
-        delegatedUIDs = frozenset((yield txn.allGroupDelegates()))
+        delegatedUIDs = set((yield txn.allGroupDelegates()))
         self.log.info(
             "There are {count} group delegates", count=len(delegatedUIDs)
         )
 
+        # Also get group delegates from other pods
+        if txn.directoryService().serversDB is not None and len(txn.directoryService().serversDB.allServersExceptThis()) != 0:
+            results = yield DeferredList([
+                txn.store().conduit.send_all_group_delegates(txn, server) for
+                server in txn.directoryService().serversDB.allServersExceptThis()
+            ], consumeErrors=True)
+            for result in results:
+                if result and result[0]:
+                    delegatedUIDs.update(result[1])
+            self.log.info(
+                "There are {count} group delegates on this and other pods", count=len(delegatedUIDs)
+            )
+
         # Get groupUIDs for all group attendees
         ga = schema.GROUP_ATTENDEE
         gr = schema.GROUPS

Modified: CalendarServer/trunk/txdav/who/test/test_delegates.py
===================================================================
--- CalendarServer/trunk/txdav/who/test/test_delegates.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/txdav/who/test/test_delegates.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -200,7 +200,7 @@
             groupID, name, _ignore_membershipHash, _ignore_modified,
             _ignore_extant
         ) = (yield txn.groupByUID(group1.uid))
-        _ignore_numAdded, _ignore_numRemoved = (
+        _ignore_added, _ignore_removed = (
             yield self.groupCacher.synchronizeMembers(txn, groupID, newSet)
         )
         delegates = (yield Delegates.delegatesOf(txn, delegator, True, expanded=True))

Modified: CalendarServer/trunk/txdav/who/test/test_groups.py
===================================================================
--- CalendarServer/trunk/txdav/who/test/test_groups.py	2014-11-12 18:05:43 UTC (rev 14155)
+++ CalendarServer/trunk/txdav/who/test/test_groups.py	2014-11-12 18:09:15 UTC (rev 14156)
@@ -131,13 +131,13 @@
                 )
             )
             newSet.add(record.uid)
-        numAdded, numRemoved = (
+        added, removed = (
             yield self.groupCacher.synchronizeMembers(
                 txn, groupID, newSet
             )
         )
-        self.assertEquals(numAdded, 1)
-        self.assertEquals(numRemoved, 2)
+        self.assertEquals(added, set(["__dre1__", ]))
+        self.assertEquals(removed, set(["__glyph1__", "__sagen1__", ]))
         records = (yield self.groupCacher.cachedMembers(txn, groupID))
         self.assertEquals(
             set([r.shortNames[0] for r in records]),
@@ -145,11 +145,11 @@
         )
 
         # Remove all members
-        numAdded, numRemoved = (
+        added, removed = (
             yield self.groupCacher.synchronizeMembers(txn, groupID, set())
         )
-        self.assertEquals(numAdded, 0)
-        self.assertEquals(numRemoved, 3)
+        self.assertEquals(added, set())
+        self.assertEquals(removed, set(["__wsanchez1__", "__cdaboo1__", "__dre1__", ]))
         records = (yield self.groupCacher.cachedMembers(txn, groupID))
         self.assertEquals(len(records), 0)
 
@@ -483,7 +483,133 @@
         yield txn.commit()
 
 
+    @inlineCallbacks
+    def test_groupChangeCacheNotification(self):
+        """
+        Verify refreshGroup() triggers a cache notification for the group and all
+        members that are added or removed
+        """
 
+        class TestNotifier(object):
+            changedTokens = []
+            def changed(self, token):
+                self.changedTokens.append(token)
+
+        self.groupCacher.cacheNotifier = TestNotifier()
+
+        # No change
+        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
+        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__sub_group_1__")
+        yield self.commit()
+
+        self.assertEqual(TestNotifier.changedTokens, [])
+
+        # Add member to group
+        record = yield self.directory.recordWithUID(u"__top_group_1__")
+        addrecord = yield self.directory.recordWithUID(u"__dre1__")
+        yield record.addMembers([addrecord, ])
+
+        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
+        yield self.commit()
+
+        self.assertEqual(TestNotifier.changedTokens, [
+            "__top_group_1__",
+            "__dre1__",
+        ])
+        TestNotifier.changedTokens = []
+
+        # Remove member from group
+        record = yield self.directory.recordWithUID(u"__top_group_1__")
+        addrecord = yield self.directory.recordWithUID(u"__dre1__")
+        yield record.removeMembers([addrecord, ])
+
+        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
+        yield self.commit()
+
+        self.assertEqual(TestNotifier.changedTokens, [
+            "__top_group_1__",
+            "__dre1__",
+        ])
+        TestNotifier.changedTokens = []
+
+        # Add member to sub-group
+        record = yield self.directory.recordWithUID(u"__sub_group_1__")
+        addrecord = yield self.directory.recordWithUID(u"__dre1__")
+        yield record.addMembers([addrecord, ])
+
+        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
+        yield self.commit()
+
+        self.assertEqual(TestNotifier.changedTokens, [
+            "__top_group_1__",
+            "__dre1__",
+        ])
+        TestNotifier.changedTokens = []
+
+        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__sub_group_1__")
+        yield self.commit()
+
+        self.assertEqual(TestNotifier.changedTokens, [
+            "__sub_group_1__",
+            "__dre1__",
+        ])
+        TestNotifier.changedTokens = []
+
+        # Remove member from sub-group
+        record = yield self.directory.recordWithUID(u"__sub_group_1__")
+        addrecord = yield self.directory.recordWithUID(u"__dre1__")
+        yield record.removeMembers([addrecord, ])
+
+        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
+        yield self.commit()
+
+        self.assertEqual(TestNotifier.changedTokens, [
+            "__top_group_1__",
+            "__dre1__",
+        ])
+        TestNotifier.changedTokens = []
+
+        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__sub_group_1__")
+        yield self.commit()
+
+        self.assertEqual(TestNotifier.changedTokens, [
+            "__sub_group_1__",
+            "__dre1__",
+        ])
+        TestNotifier.changedTokens = []
+
+        # Remove sub-group member from group
+        record = yield self.directory.recordWithUID(u"__top_group_1__")
+        addrecord = yield self.directory.recordWithUID(u"__sub_group_1__")
+        yield record.removeMembers([addrecord, ])
+
+        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
+        yield self.commit()
+
+        self.assertEqual(TestNotifier.changedTokens, [
+            "__top_group_1__",
+            "__sagen1__",
+            "__cdaboo1__",
+        ])
+        TestNotifier.changedTokens = []
+
+        # Add sub-group member to group
+        record = yield self.directory.recordWithUID(u"__top_group_1__")
+        addrecord = yield self.directory.recordWithUID(u"__sub_group_1__")
+        yield record.addMembers([addrecord, ])
+
+        yield self.groupCacher.refreshGroup(self.transactionUnderTest(), "__top_group_1__")
+        yield self.commit()
+
+        self.assertEqual(TestNotifier.changedTokens, [
+            "__top_group_1__",
+            "__sagen1__",
+            "__cdaboo1__",
+        ])
+        TestNotifier.changedTokens = []
+
+
+
 class DynamicGroupTest(StoreTestCase):
 
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20141112/0fb6dd1b/attachment-0001.html>


More information about the calendarserver-changes mailing list