[CalendarServer-changes] [11051] CalendarServer/branches/users/glyph/sharedgroups-2
source_changes at macosforge.org
source_changes at macosforge.org
Tue Apr 16 15:19:02 PDT 2013
Revision: 11051
http://trac.calendarserver.org//changeset/11051
Author: glyph at apple.com
Date: 2013-04-16 15:19:02 -0700 (Tue, 16 Apr 2013)
Log Message:
-----------
Up to 10980.
Modified Paths:
--------------
CalendarServer/branches/users/glyph/sharedgroups-2/calendarserver/tap/caldav.py
CalendarServer/branches/users/glyph/sharedgroups-2/twisted/plugins/caldav.py
CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/directory/directory.py
CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/directory/test/test_directory.py
CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/ical.py
CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/method/put_common.py
CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/scheduling/imip/inbound.py
CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/stdconfig.py
CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/test/test_icalendar.py
CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/test/test_validation.py
CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/upgrade.py
CalendarServer/branches/users/glyph/sharedgroups-2/txdav/common/datastore/sql_schema/current.sql
Added Paths:
-----------
CalendarServer/branches/users/glyph/sharedgroups-2/.gitignore
Property Changed:
----------------
CalendarServer/branches/users/glyph/sharedgroups-2/
Property changes on: CalendarServer/branches/users/glyph/sharedgroups-2
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:9885-10970
+ /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:9885-10980
Copied: CalendarServer/branches/users/glyph/sharedgroups-2/.gitignore (from rev 10980, CalendarServer/trunk/.gitignore)
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/.gitignore (rev 0)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/.gitignore 2013-04-16 22:19:02 UTC (rev 11051)
@@ -0,0 +1,8 @@
+*.pyc
+*.so
+dropin.cache
+
+/build/
+/data/
+/calendarserver/version.py
+/conf/caldavd-dev.plist
\ No newline at end of file
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/calendarserver/tap/caldav.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/calendarserver/tap/caldav.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -70,6 +70,8 @@
from twistedcaldav.config import ConfigurationError
from twistedcaldav.config import config
+from twistedcaldav.directory import calendaruserproxy
+from twistedcaldav.directory.directory import GroupMembershipCacheUpdater
from twistedcaldav.localization import processLocalizationFiles
from twistedcaldav import memcachepool
from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
@@ -542,29 +544,8 @@
)
self.monitor.addProcessObject(process, PARENT_ENVIRONMENT)
- if config.GroupCaching.Enabled and config.GroupCaching.EnableUpdater:
- self.maker.log_info("Adding group caching service")
- groupMembershipCacherArgv = [
- sys.executable,
- sys.argv[0],
- ]
- if config.UserName:
- groupMembershipCacherArgv.extend(("-u", config.UserName))
- if config.GroupName:
- groupMembershipCacherArgv.extend(("-g", config.GroupName))
- groupMembershipCacherArgv.extend((
- "--reactor=%s" % (config.Twisted.reactor,),
- "-n", self.maker.groupMembershipCacherTapName,
- "-f", self.configPath,
- "-o", "PIDFile=groupcacher.pid",
- ))
- self.monitor.addProcess("groupcacher", groupMembershipCacherArgv,
- env=PARENT_ENVIRONMENT)
-
-
-
class ReExecService(MultiService, LoggingMixIn):
"""
A MultiService which catches SIGHUP and re-exec's the process.
@@ -621,12 +602,7 @@
description = "Calendar and Contacts Server"
options = CalDAVOptions
- #
- # Default tap names
- #
- groupMembershipCacherTapName = "caldav_groupcacher"
-
def makeService(self, options):
"""
Create the top-level service.
@@ -774,10 +750,24 @@
else:
mailRetriever = None
+ # Optionally set up group cacher
+ if config.GroupCaching.Enabled:
+ groupCacher = GroupMembershipCacheUpdater(
+ calendaruserproxy.ProxyDBService,
+ directory,
+ config.GroupCaching.UpdateSeconds,
+ config.GroupCaching.ExpireSeconds,
+ namespace=config.GroupCaching.MemcachedPool,
+ useExternalProxies=config.GroupCaching.UseExternalProxies
+ )
+ else:
+ groupCacher = None
+
def decorateTransaction(txn):
txn._pushDistributor = pushDistributor
txn._rootResource = result.rootResource
txn._mailRetriever = mailRetriever
+ txn._groupCacher = groupCacher
store.callWithNewTransactions(decorateTransaction)
@@ -1016,11 +1006,35 @@
def makeService_Single(self, options):
"""
Create a service to be used in a single-process, stand-alone
- configuration.
+ configuration. Memcached will be spawned automatically.
"""
def slaveSvcCreator(pool, store, logObserver):
result = self.requestProcessingService(options, store, logObserver)
+ # Optionally launch memcached. Note, this is not going through a
+ # ProcessMonitor because there is code elsewhere that needs to
+ # access memcached before startService() gets called, so we're just
+ # directly using Popen to spawn memcached.
+ for name, pool in config.Memcached.Pools.items():
+ if pool.ServerEnabled:
+ self.log_info(
+ "Adding memcached service for pool: %s" % (name,)
+ )
+ memcachedArgv = [
+ config.Memcached.memcached,
+ "-p", str(pool.Port),
+ "-l", pool.BindAddress,
+ "-U", "0",
+ ]
+ if config.Memcached.MaxMemory is not 0:
+ memcachedArgv.extend(
+ ["-m", str(config.Memcached.MaxMemory)]
+ )
+ if config.UserName:
+ memcachedArgv.extend(["-u", config.UserName])
+ memcachedArgv.extend(config.Memcached.Options)
+ Popen(memcachedArgv)
+
# Optionally set up push notifications
pushDistributor = None
if config.Notifications.Enabled:
@@ -1049,10 +1063,24 @@
else:
mailRetriever = None
+ # Optionally set up group cacher
+ if config.GroupCaching.Enabled:
+ groupCacher = GroupMembershipCacheUpdater(
+ calendaruserproxy.ProxyDBService,
+ directory,
+ config.GroupCaching.UpdateSeconds,
+ config.GroupCaching.ExpireSeconds,
+ namespace=config.GroupCaching.MemcachedPool,
+ useExternalProxies=config.GroupCaching.UseExternalProxies
+ )
+ else:
+ groupCacher = None
+
def decorateTransaction(txn):
txn._pushDistributor = pushDistributor
txn._rootResource = result.rootResource
txn._mailRetriever = mailRetriever
+ txn._groupCacher = groupCacher
store.callWithNewTransactions(decorateTransaction)
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/twisted/plugins/caldav.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/twisted/plugins/caldav.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/twisted/plugins/caldav.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -53,4 +53,3 @@
TwistedCalDAV = TAP("calendarserver.tap.caldav.CalDAVServiceMaker")
-CalDAVGroupCacher = TAP("twistedcaldav.directory.directory.GroupMembershipCacherServiceMaker")
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/directory/directory.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/directory/directory.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -26,6 +26,7 @@
"DirectoryError",
"DirectoryConfigurationError",
"UnknownRecordTypeError",
+ "GroupMembershipCacheUpdater",
]
import cPickle as pickle
@@ -34,7 +35,6 @@
import itertools
import os
import pwd
-import signal
import sys
import types
@@ -46,24 +46,27 @@
from twext.web2.dav.auth import IPrincipalCredentials
from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-from twext.python.log import LoggingMixIn
+from twext.python.log import Logger, LoggingMixIn
from twistedcaldav.config import config
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+
from twistedcaldav.directory.idirectory import IDirectoryService, IDirectoryRecord
from twistedcaldav.directory.util import uuidFromName, normalizeUUID
from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
from twistedcaldav.scheduling.ischedule.localservers import Servers
from twistedcaldav.memcacher import Memcacher
-from twistedcaldav import memcachepool
from twisted.python.filepath import FilePath
-from twisted.python.reflect import namedClass
-from twisted.python.usage import Options, UsageError
-from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
-from twisted.application import service
-from twisted.plugin import IPlugin
from xml.parsers.expat import ExpatError
from plistlib import readPlistFromString
+from twext.enterprise.dal.record import fromTable
+from twext.enterprise.queue import WorkItem
+from txdav.common.datastore.sql_tables import schema
+from twext.enterprise.dal.syntax import Delete
+log = Logger()
+
+
class DirectoryService(LoggingMixIn):
implements(IDirectoryService, ICredentialsChecker)
@@ -564,20 +567,16 @@
"group-cacher-populated" : contains a datestamp indicating the most recent
population.
-
- "group-cacher-lock" : used to prevent multiple updates, it has a value of "1"
-
"""
def __init__(self, namespace, pickle=True, no_invalidation=False,
- key_normalization=True, expireSeconds=0, lockSeconds=60):
+ key_normalization=True, expireSeconds=0):
super(GroupMembershipCache, self).__init__(namespace, pickle=pickle,
no_invalidation=no_invalidation,
key_normalization=key_normalization)
self.expireSeconds = expireSeconds
- self.lockSeconds = lockSeconds
def setGroupsFor(self, guid, memberships):
@@ -616,17 +615,7 @@
returnValue(value is not None)
- def acquireLock(self):
- self.log_debug("add group-cacher-lock")
- return self.add("group-cacher-lock", "1", expireTime=self.lockSeconds)
-
- def releaseLock(self):
- self.log_debug("delete group-cacher-lock")
- return self.delete("group-cacher-lock")
-
-
-
class GroupMembershipCacheUpdater(LoggingMixIn):
"""
Responsible for updating memcached with group memberships. This will run
@@ -634,11 +623,12 @@
proxy database, and the location/resource info in the directory system.
"""
- def __init__(self, proxyDB, directory, expireSeconds, lockSeconds,
+ def __init__(self, proxyDB, directory, updateSeconds, expireSeconds,
cache=None, namespace=None, useExternalProxies=False,
externalProxiesSource=None):
self.proxyDB = proxyDB
self.directory = directory
+ self.updateSeconds = updateSeconds
self.useExternalProxies = useExternalProxies
if useExternalProxies and externalProxiesSource is None:
externalProxiesSource = self.directory.getExternalProxyAssignments
@@ -646,8 +636,7 @@
if cache is None:
assert namespace is not None, "namespace must be specified if GroupMembershipCache is not provided"
- cache = GroupMembershipCache(namespace, expireSeconds=expireSeconds,
- lockSeconds=lockSeconds)
+ cache = GroupMembershipCache(namespace, expireSeconds=expireSeconds)
self.cache = cache
@@ -738,8 +727,6 @@
# See if anyone has completely populated the group membership cache
isPopulated = (yield self.cache.isPopulated())
- useLock = True
-
if fast:
# We're in quick-start mode. Check first to see if someone has
# populated the membership cache, and if so, return immediately
@@ -747,9 +734,6 @@
self.log_info("Group membership cache is already populated")
returnValue((fast, 0))
- # We don't care what others are doing right now, we need to update
- useLock = False
-
self.log_info("Updating group membership cache")
dataRoot = FilePath(config.DataRoot)
@@ -767,14 +751,6 @@
previousMembers = pickle.loads(membershipsCacheFile.getContent())
callGroupsChanged = True
- if useLock:
- self.log_info("Attempting to acquire group membership cache lock")
- acquiredLock = (yield self.cache.acquireLock())
- if not acquiredLock:
- self.log_info("Group membership cache lock held by another process")
- returnValue((fast, 0))
- self.log_info("Acquired lock")
-
if not fast and self.useExternalProxies:
# Load in cached copy of external proxies so we can diff against them
@@ -944,257 +920,44 @@
yield self.cache.setPopulatedMarker()
- if useLock:
- self.log_info("Releasing lock")
- yield self.cache.releaseLock()
-
self.log_info("Group memberships cache updated")
returnValue((fast, len(members), len(changedMembers)))
+class GroupCacherPollingWork(WorkItem, fromTable(schema.GROUP_CACHER_POLLING_WORK)):
-class GroupMembershipCacherOptions(Options):
- optParameters = [[
- "config", "f", DEFAULT_CONFIG_FILE, "Path to configuration file."
- ]]
+ group = "group_cacher_polling"
- def __init__(self, *args, **kwargs):
- super(GroupMembershipCacherOptions, self).__init__(*args, **kwargs)
-
- self.overrides = {}
-
-
- def _coerceOption(self, configDict, key, value):
- """
- Coerce the given C{val} to type of C{configDict[key]}
- """
- if key in configDict:
- if isinstance(configDict[key], bool):
- value = value == "True"
-
- elif isinstance(configDict[key], (int, float, long)):
- value = type(configDict[key])(value)
-
- elif isinstance(configDict[key], (list, tuple)):
- value = value.split(',')
-
- elif isinstance(configDict[key], dict):
- raise UsageError(
- "Dict options not supported on the command line"
- )
-
- elif value == 'None':
- value = None
-
- return value
-
-
- def _setOverride(self, configDict, path, value, overrideDict):
- """
- Set the value at path in configDict
- """
- key = path[0]
-
- if len(path) == 1:
- overrideDict[key] = self._coerceOption(configDict, key, value)
- return
-
- if key in configDict:
- if not isinstance(configDict[key], dict):
- raise UsageError(
- "Found intermediate path element that is not a dictionary"
- )
-
- if key not in overrideDict:
- overrideDict[key] = {}
-
- self._setOverride(
- configDict[key], path[1:],
- value, overrideDict[key]
- )
-
-
- def opt_option(self, option):
- """
- Set an option to override a value in the config file. True, False, int,
- and float options are supported, as well as comma seperated lists. Only
- one option may be given for each --option flag, however multiple
- --option flags may be specified.
- """
-
- if "=" in option:
- path, value = option.split('=')
- self._setOverride(
- DEFAULT_CONFIG,
- path.split('/'),
- value,
- self.overrides
- )
- else:
- self.opt_option('%s=True' % (option,))
-
- opt_o = opt_option
-
- def postOptions(self):
- config.load(self['config'])
- config.updateDefaults(self.overrides)
- self.parent['pidfile'] = config.PIDFile
-
-
-
-class GroupMembershipCacherService(service.Service, LoggingMixIn):
- """
- Service to update the group membership cache at a configured interval
- """
-
- def __init__(self, proxyDB, directory, namespace, updateSeconds,
- expireSeconds, lockSeconds, reactor=None, updateMethod=None,
- useExternalProxies=False):
-
- if updateSeconds >= expireSeconds:
- expireSeconds = updateSeconds * 2
- self.log_warn("Configuration warning: GroupCaching.ExpireSeconds needs to be longer than UpdateSeconds; setting to %d seconds" % (expireSeconds,))
-
- self.updater = GroupMembershipCacheUpdater(proxyDB, directory,
- expireSeconds, lockSeconds, namespace=namespace,
- useExternalProxies=useExternalProxies)
-
- if reactor is None:
- from twisted.internet import reactor
- self.reactor = reactor
-
- self.updateSeconds = updateSeconds
- self.nextUpdate = None
- self.updateInProgress = False
- self.updateAwaiting = False
-
- if updateMethod:
- self.updateMethod = updateMethod
- else:
- self.updateMethod = self.updater.updateCache
-
-
- def startService(self):
- self.previousHandler = signal.signal(signal.SIGHUP, self.sighupHandler)
- self.log_warn("Starting group membership cacher service")
- service.Service.startService(self)
- return self.update()
-
-
- def sighupHandler(self, num, frame):
- self.reactor.callFromThread(self.update)
-
-
- def stopService(self):
- signal.signal(signal.SIGHUP, self.previousHandler)
- self.log_warn("Stopping group membership cacher service")
- service.Service.stopService(self)
- if self.nextUpdate is not None:
- self.nextUpdate.cancel()
- self.nextUpdate = None
-
-
@inlineCallbacks
- def update(self):
- """
- A wrapper around updateCache, this method manages the scheduling of the
- subsequent update, as well as prevents multiple updates from running
- simultaneously, which could otherwise happen because SIGHUP now triggers
- an update on demand. If update is called while an update is in progress,
- as soon as the first update is finished a new one is started. Otherwise,
- when an update finishes and there is not another one waiting, the next
- update is scheduled for updateSeconds in the future.
+ def doWork(self):
- @return: True if an update was already in progress, False otherwise
- @rtype: C{bool}
- """
+ # Delete all other work items
+ yield Delete(From=self.table, Where=None).on(self.transaction)
- self.log_debug("Group membership update called")
+ groupCacher = self.transaction._groupCacher
+ if groupCacher is not None:
+ try:
+ yield groupCacher.updateCache()
+ except Exception, e:
+ log.error("Failed to update group membership cache (%s)" % (e,))
+ finally:
+ notBefore = (datetime.datetime.utcnow() +
+ datetime.timedelta(seconds=groupCacher.updateSeconds))
+ log.debug("Scheduling next group cacher update: %s" % (notBefore,))
+ yield self.transaction.enqueue(GroupCacherPollingWork,
+ notBefore=notBefore)
- # A call to update while an update is in progress sets the updateAwaiting flag
- # so that an update happens again right after the current one is complete.
- if self.updateInProgress:
- self.updateAwaiting = True
- returnValue(True)
- self.nextUpdate = None
- self.updateInProgress = True
- self.updateAwaiting = False
- try:
- yield self.updateMethod()
- finally:
- self.updateInProgress = False
- if self.updateAwaiting:
- self.log_info("Performing group membership update")
- yield self.update()
- else:
- self.log_info("Scheduling next group membership update")
- self.nextUpdate = self.reactor.callLater(self.updateSeconds,
- self.update)
- returnValue(False)
+ at inlineCallbacks
+def scheduleNextGroupCachingUpdate(store, seconds):
+ txn = store.newTransaction()
+ notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds)
+ log.debug("Scheduling next group cacher update: %s" % (notBefore,))
+ yield txn.enqueue(GroupCacherPollingWork, notBefore=notBefore)
+ yield txn.commit()
-
-class GroupMembershipCacherServiceMaker(LoggingMixIn):
- """
- Configures and returns a GroupMembershipCacherService
- """
- implements(IPlugin, service.IServiceMaker)
-
- tapname = "caldav_groupcacher"
- description = "Group Membership Cacher"
- options = GroupMembershipCacherOptions
-
- def makeService(self, options):
- try:
- from setproctitle import setproctitle
- except ImportError:
- pass
- else:
- setproctitle("CalendarServer [Group Cacher]")
-
- # Setup the directory
- from calendarserver.tap.util import directoryFromConfig
- directory = directoryFromConfig(config)
-
- # We have to set cacheNotifierFactory otherwise group cacher can't
- # invalidate the cache tokens for principals whose membership has
- # changed
- if config.EnableResponseCache and config.Memcached.Pools.Default.ClientEnabled:
- from twistedcaldav.directory.principal import DirectoryPrincipalResource
- from twistedcaldav.cache import MemcacheChangeNotifier
- DirectoryPrincipalResource.cacheNotifierFactory = MemcacheChangeNotifier
-
- # Setup the ProxyDB Service
- proxydbClass = namedClass(config.ProxyDBService.type)
-
- self.log_warn("Configuring proxydb service of type: %s" % (proxydbClass,))
-
- try:
- proxyDB = proxydbClass(**config.ProxyDBService.params)
- except IOError:
- self.log_error("Could not start proxydb service")
- raise
-
- # Setup memcached pools
- memcachepool.installPools(
- config.Memcached.Pools,
- config.Memcached.MaxClients,
- )
-
- cacherService = GroupMembershipCacherService(proxyDB, directory,
- config.GroupCaching.MemcachedPool,
- config.GroupCaching.UpdateSeconds,
- config.GroupCaching.ExpireSeconds,
- config.GroupCaching.LockSeconds,
- useExternalProxies=config.GroupCaching.UseExternalProxies
- )
-
- return cacherService
-
-
-
def diffAssignments(old, new):
"""
Compare two proxy assignment lists and return their differences in the form of
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/directory/test/test_directory.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/directory/test/test_directory.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/directory/test/test_directory.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -15,13 +15,12 @@
##
from twisted.internet.defer import inlineCallbacks
-from twisted.internet.task import Clock
from twisted.python.filepath import FilePath
from twistedcaldav.test.util import TestCase
from twistedcaldav.test.util import xmlFile, augmentsFile, proxiesFile, dirTest
from twistedcaldav.config import config
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord, GroupMembershipCacherService, GroupMembershipCache, GroupMembershipCacheUpdater, diffAssignments
+from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord, GroupMembershipCache, GroupMembershipCacheUpdater, diffAssignments
from twistedcaldav.directory.xmlfile import XMLDirectoryService
from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
from twistedcaldav.directory import augment, calendaruserproxy
@@ -121,46 +120,7 @@
self.count += 1
- @inlineCallbacks
- def test_groupMembershipCacherService(self):
- """
- Instantiate a GroupMembershipCacherService and make sure its update
- method fires at the right interval, in this case 30 seconds. The
- updateMethod keyword arg is purely for testing purposes, so we can
- directly detect it getting called in this test.
- """
- clock = Clock()
- self.count = 0
- # Deliberately set the expireSeconds lower than updateSeconds to verify
- # expireSeconds gets set to 2 * updateSeconds in that scenario
-
- service = GroupMembershipCacherService(
- None, None, "Testing", 30, 20, 30, reactor=clock,
- updateMethod=self._updateMethod)
-
- # expireSeconds = 2 * 30 updateSeconds
- self.assertEquals(service.updater.cache.expireSeconds, 60)
-
- yield service.startService()
-
- self.assertEquals(self.count, 1)
- clock.advance(29)
- self.assertEquals(self.count, 1)
- clock.advance(1)
- self.assertEquals(self.count, 2)
-
- service.stopService()
-
- service.updateInProgress = True
- self.assertTrue((yield service.update()))
- self.assertTrue(service.updateAwaiting)
-
- service.updateInProgress = False
- self.assertFalse((yield service.update()))
- self.assertFalse(service.updateAwaiting)
-
-
def test_expandedMembers(self):
"""
Make sure expandedMembers( ) returns a complete, flattened set of
@@ -279,17 +239,6 @@
)
)
- # Prevent an update by locking the cache
- acquiredLock = (yield cache.acquireLock())
- self.assertTrue(acquiredLock)
- self.assertEquals((False, 0), (yield updater.updateCache()))
-
- # You can't lock when already locked:
- acquiredLockAgain = (yield cache.acquireLock())
- self.assertFalse(acquiredLockAgain)
-
- # Allow an update by unlocking the cache
- yield cache.releaseLock()
self.assertEquals((False, 9, 9), (yield updater.updateCache()))
# Verify cache is populated:
@@ -686,10 +635,6 @@
# time), but since the snapshot doesn't exist we fault in from the
# directory (fast now is False), and snapshot will get created
- # Note that because fast=True and isPopulated() is False, locking is
- # ignored:
- yield cache.acquireLock()
-
self.assertFalse((yield cache.isPopulated()))
fast, numMembers, numChanged = (yield updater.updateCache(fast=True))
self.assertEquals(fast, False)
@@ -698,8 +643,6 @@
self.assertTrue(snapshotFile.exists())
self.assertTrue((yield cache.isPopulated()))
- yield cache.releaseLock()
-
# Try another fast update where the snapshot already exists (as in a
# server-restart scenario), which will only read from the snapshot
# as indicated by the return value for "fast". Note that the cache
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/ical.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/ical.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -2979,8 +2979,7 @@
# Check that we can lookup this calendar user address - if not
# we cannot do anything with it
cuaddr = normalizeCUAddr(prop.value())
- name, guid, cuaddrs = lookupFunction(cuaddr, principalFunction,
- config)
+ name, guid, cuaddrs = lookupFunction(cuaddr, principalFunction, config)
if guid is None:
continue
@@ -2989,6 +2988,11 @@
if oldemail:
oldemail = "mailto:%s" % (oldemail,)
+ # Get any CN parameter
+ oldCN = prop.parameterValue("CN")
+
+ cutype = prop.parameterValue("CUTYPE")
+
if toUUID:
# Store the original CUA if http(s) or /path:
if config.Scheduling.Options.V1Compatibility:
@@ -3049,9 +3053,17 @@
if newaddr:
prop.setValue(newaddr)
- # Always re-write the CN parameter
+ # Re-write the CN parameter
if name:
- prop.setParameter("CN", name)
+ if name != oldCN:
+ prop.setParameter("CN", name)
+
+ # Also adjust any previously matching location property
+ if cutype == "ROOM":
+ location = component.getProperty("LOCATION")
+ if location is not None:
+ if location.value() == oldCN:
+ location.setValue(name)
else:
prop.removeParameter("CN")
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/method/put_common.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/method/put_common.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -203,6 +203,7 @@
self.access = None
self.hasPrivateComments = False
self.isScheduleResource = False
+ self.dataChanged = False
@inlineCallbacks
@@ -365,6 +366,9 @@
# Check that moves to shared calendars are OK
yield self.validCopyMoveOperation()
+ # Check location/resource organizer requirement
+ yield self.validLocationResourceOrganizer()
+
# Check access
if self.destinationcal and config.EnablePrivateEvents:
result = (yield self.validAccess())
@@ -662,6 +666,53 @@
@inlineCallbacks
+ def validLocationResourceOrganizer(self):
+ """
+ If the calendar owner is a location or resource, check whether an ORGANIZER property is required.
+ """
+
+ if not self.internal_request:
+ originatorPrincipal = (yield self.destination.ownerPrincipal(self.request))
+ cutype = originatorPrincipal.getCUType() if originatorPrincipal is not None else "INDIVIDUAL"
+ organizer = self.calendar.getOrganizer()
+
+ # Check for an allowed change
+ if organizer is None and (
+ cutype == "ROOM" and not config.Scheduling.Options.AllowLocationWithoutOrganizer or
+ cutype == "RESOURCE" and not config.Scheduling.Options.AllowResourceWithoutOrganizer):
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
+ (calendarserver_namespace, "valid-organizer"),
+ "Organizer required in calendar data",
+ ))
+
+ # Check for tracking the modifier
+ if organizer is None and (
+ cutype == "ROOM" and config.Scheduling.Options.TrackUnscheduledLocationData or
+ cutype == "RESOURCE" and config.Scheduling.Options.TrackUnscheduledResourceData):
+
+ # Find current principal
+ authz = None
+ authz_principal = self.destinationparent.currentPrincipal(self.request).children[0]
+ if isinstance(authz_principal, davxml.HRef):
+ principalURL = str(authz_principal)
+ if principalURL:
+ authz = (yield self.request.locateResource(principalURL))
+
+ if authz is not None:
+ prop = Property("X-CALENDARSERVER-MODIFIED-BY", "urn:uuid:%s" % (authz.record.guid,))
+ prop.setParameter("CN", authz.displayName())
+ for candidate in authz.calendarUserAddresses():
+ if candidate.startswith("mailto:"):
+ prop.setParameter("EMAIL", candidate[7:])
+ break
+ self.calendar.replacePropertyInAllComponents(prop)
+ else:
+ self.calendar.removeAllPropertiesWithName("X-CALENDARSERVER-MODIFIED-BY")
+ self.dataChanged = True
+
+
+ @inlineCallbacks
def preservePrivateComments(self):
# Check for private comments on the old resource and the new resource and re-insert
# ones that are lost.
@@ -769,7 +820,6 @@
"""
# Only relevant if calendar is sharee collection
- changed = False
if self.destinationparent.isShareeResource():
# Get all X-APPLE-DROPBOX's and ATTACH's that are http URIs
@@ -813,59 +863,52 @@
uri = uriNormalize(xdropbox.value())
if uri:
xdropbox.setValue(uri)
- changed = True
+ self.dataChanged = True
for attachment in attachments:
uri = uriNormalize(attachment.value())
if uri:
attachment.setValue(uri)
- changed = True
+ self.dataChanged = True
- returnValue(changed)
-
def processAlarms(self):
"""
Remove duplicate alarms. Add a default alarm if required.
-
- @return: indicate whether a change was made
- @rtype: C{bool}
"""
# Remove duplicate alarms
- changed = False
- if config.RemoveDuplicateAlarms:
- changed = self.calendar.hasDuplicateAlarms(doFix=True)
+ if config.RemoveDuplicateAlarms and self.calendar.hasDuplicateAlarms(doFix=True):
+ self.dataChanged = True
# Only if feature enabled
if not config.EnableDefaultAlarms:
- return changed
+ return
# Check that we are creating and this is not the inbox
if not self.destinationcal or self.destination.exists() or self.isiTIP:
- return changed
+ return
# Never add default alarms to calendar data in shared calendars
if self.destinationparent.isShareeResource():
- return changed
+ return
# Add default alarm for VEVENT and VTODO only
mtype = self.calendar.mainType().upper()
if self.calendar.mainType().upper() not in ("VEVENT", "VTODO"):
- return changed
+ return
vevent = mtype == "VEVENT"
# Check timed or all-day
start, _ignore_end = self.calendar.mainComponent(allow_multiple=True).getEffectiveStartEnd()
if start is None:
# Yes VTODOs might have no DTSTART or DUE - in this case we do not add a default
- return changed
+ return
timed = not start.isDateOnly()
# See if default exists and add using appropriate logic
alarm = self.destinationparent.getDefaultAlarm(vevent, timed)
- if alarm:
- changed = self.calendar.addAlarms(alarm)
- return changed
+ if alarm and self.calendar.addAlarms(alarm):
+ self.dataChanged = True
@inlineCallbacks
@@ -1221,14 +1264,14 @@
yield self.replaceMissingToDoProperties()
# Handle sharing dropbox normalization
- dropboxChanged = (yield self.dropboxPathNormalization())
+ yield self.dropboxPathNormalization()
# Pre-process managed attachments
if not self.internal_request and not self.attachmentProcessingDone:
managed_copied, managed_removed = (yield self.destination.preProcessManagedAttachments(self.calendar))
# Default/duplicate alarms
- alarmChanged = self.processAlarms()
+ self.processAlarms()
# Do scheduling
implicit_result = (yield self.doImplicitScheduling())
@@ -1276,7 +1319,7 @@
yield self.destination.postProcessManagedAttachments(managed_copied, managed_removed)
# Must not set ETag in response if data changed
- if did_implicit_action or dropboxChanged or alarmChanged:
+ if did_implicit_action or self.dataChanged:
def _removeEtag(request, response):
response.headers.removeHeader('etag')
return response
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/scheduling/imip/inbound.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/scheduling/imip/inbound.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/scheduling/imip/inbound.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -120,22 +120,25 @@
self.point = GAIEndpoint(self.reactor, settings.Server,
settings.Port, contextFactory=contextFactory)
- def startService(self):
- return self.scheduleNextPoll(seconds=0)
-
def fetchMail(self):
return self.point.connect(self.factory(self.settings, self.mailReceiver))
+
@inlineCallbacks
def scheduleNextPoll(self, seconds=None):
if seconds is None:
seconds = self.settings["PollingSeconds"]
- txn = self.store.newTransaction()
- notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds)
- yield txn.enqueue(IMIPPollingWork, notBefore=notBefore)
- yield txn.commit()
+ yield scheduleNextMailPoll(self.store, seconds)
+
+ at inlineCallbacks
+def scheduleNextMailPoll(store, seconds):
+ txn = store.newTransaction()
+ notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=seconds)
+ log.debug("Scheduling next mail poll: %s" % (notBefore,))
+ yield txn.enqueue(IMIPPollingWork, notBefore=notBefore)
+ yield txn.commit()
class MailReceiver(object):
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/stdconfig.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/stdconfig.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -694,6 +694,10 @@
"AllowGroupAsOrganizer" : False, # Allow groups to be Organizers
"AllowLocationAsOrganizer" : False, # Allow locations to be Organizers
"AllowResourceAsOrganizer" : False, # Allow resources to be Organizers
+ "AllowLocationWithoutOrganizer" : True, # Allow locations to have events without an Organizer
+ "AllowResourceWithoutOrganizer" : True, # Allow resources to have events without an Organizer
+ "TrackUnscheduledLocationData" : True, # Track who the last modifier of an unscheduled location event is
+ "TrackUnscheduledResourceData" : True, # Track who the last modifier of an unscheduled resource event is
"LimitFreeBusyAttendees" : 30, # Maximum number of attendees to request freebusy for
"AttendeeRefreshBatch" : 5, # Number of attendees to do batched refreshes: 0 - no batching
"AttendeeRefreshBatchDelaySeconds" : 5, # Time after an iTIP REPLY for first batched attendee refresh
@@ -927,7 +931,6 @@
"MemcachedPool" : "Default",
"UpdateSeconds" : 300,
"ExpireSeconds" : 3600,
- "LockSeconds" : 300,
"EnableUpdater" : True,
"UseExternalProxies" : False,
},
@@ -1035,6 +1038,7 @@
return configDict
+
def _expandPath(path):
if '$' in path:
return path.replace('$', getfqdn())
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/test/test_icalendar.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/test/test_icalendar.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -7432,6 +7432,165 @@
"base64-aHR0cDovL2V4YW1wbGUuY29tL3ByaW5jaXBhbHMvdXNlcnMvYnV6")
+ def test_normalizeCalendarUserAddressesAndLocationChange(self):
+ """
+ Ensure http(s) and /path CUA values are tucked away into the property
+ using CALENDARSERVER-OLD-CUA parameter.
+ """
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+DTSTART:20071114T000000Z
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20071114T000000Z
+ATTENDEE:/principals/users/foo
+ATTENDEE:http://example.com/principals/users/bar
+ATTENDEE;CN=Buzz;CUTYPE=ROOM:http://example.com/principals/locations/buzz
+LOCATION:Buzz
+DTSTAMP:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+ component = Component.fromString(data)
+
+
+ def lookupFunction(cuaddr, ignored1, ignored2):
+ return {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar",)
+ ),
+ "http://example.com/principals/locations/buzz" : (
+ "{Restricted} Buzz",
+ "buzz",
+ ("urn:uuid:buzz",)
+ ),
+ }[cuaddr]
+
+ component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
+
+ # Location value changed
+ prop = component.mainComponent().getProperty("LOCATION")
+ self.assertEquals(prop.value(), "{Restricted} Buzz")
+ prop = component.getAttendeeProperty(("urn:uuid:buzz",))
+ self.assertEquals("urn:uuid:buzz", prop.value())
+ self.assertEquals(prop.parameterValue("CN"), "{Restricted} Buzz")
+
+
+ def test_normalizeCalendarUserAddressesAndLocationNoChange(self):
+ """
+ Ensure http(s) and /path CUA values are tucked away into the property
+ using CALENDARSERVER-OLD-CUA parameter.
+ """
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+DTSTART:20071114T000000Z
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20071114T000000Z
+ATTENDEE:/principals/users/foo
+ATTENDEE:http://example.com/principals/users/bar
+ATTENDEE;CN=Buzz;CUTYPE=ROOM:http://example.com/principals/locations/buzz
+LOCATION:Fuzz
+DTSTAMP:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+ component = Component.fromString(data)
+
+
+ def lookupFunction(cuaddr, ignored1, ignored2):
+ return {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar",)
+ ),
+ "http://example.com/principals/locations/buzz" : (
+ "{Restricted} Buzz",
+ "buzz",
+ ("urn:uuid:buzz",)
+ ),
+ }[cuaddr]
+
+ component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
+
+ # Location value changed
+ prop = component.mainComponent().getProperty("LOCATION")
+ self.assertEquals(prop.value(), "Fuzz")
+ prop = component.getAttendeeProperty(("urn:uuid:buzz",))
+ self.assertEquals("urn:uuid:buzz", prop.value())
+ self.assertEquals(prop.parameterValue("CN"), "{Restricted} Buzz")
+
+
+ def test_normalizeCalendarUserAddressesAndLocationNoChangeOtherCUType(self):
+ """
+ Ensure http(s) and /path CUA values are tucked away into the property
+ using CALENDARSERVER-OLD-CUA parameter.
+ """
+
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+DTSTART:20071114T000000Z
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20071114T000000Z
+ATTENDEE:/principals/users/foo
+ATTENDEE:http://example.com/principals/users/bar
+ATTENDEE;CN=Buzz;CUTYPE=RESOURCE:http://example.com/principals/locations/buzz
+LOCATION:Buzz
+DTSTAMP:20071114T000000Z
+END:VEVENT
+END:VCALENDAR
+"""
+
+ component = Component.fromString(data)
+
+
+ def lookupFunction(cuaddr, ignored1, ignored2):
+ return {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar",)
+ ),
+ "http://example.com/principals/locations/buzz" : (
+ "{Restricted} Buzz",
+ "buzz",
+ ("urn:uuid:buzz",)
+ ),
+ }[cuaddr]
+
+ component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
+
+ # Location value changed
+ prop = component.mainComponent().getProperty("LOCATION")
+ self.assertEquals(prop.value(), "Buzz")
+ prop = component.getAttendeeProperty(("urn:uuid:buzz",))
+ self.assertEquals("urn:uuid:buzz", prop.value())
+ self.assertEquals(prop.parameterValue("CN"), "{Restricted} Buzz")
+
+
def test_serializationCaching(self):
data = """BEGIN:VCALENDAR
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/test/test_validation.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/test/test_validation.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/test/test_validation.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -23,6 +23,7 @@
# to address this to use system twisted.
from twext.web2.test.test_server import SimpleRequest
from twext.web2.http import HTTPError
+from twext.web2.resource import Resource
from twistedcaldav.config import config
from twistedcaldav.ical import Component, Property
@@ -31,16 +32,20 @@
from twistedcaldav.resource import CalDAVResource
class InMemoryCalendarObjectResource(CalDAVResource):
-
+
def exists(self):
return hasattr(self, "_data") and self._data is not None
+
def iCalendarForUser(self, user):
return self._data
-
+
+
def setData(self, data):
self._data = data
+
+
class TestCopyMoveValidation(TestCase):
"""
Tests for the validation code in L{twistedcaldav.method.put_common}.
@@ -52,11 +57,12 @@
"""
self.destination = InMemoryCalendarObjectResource()
- self.destination.name = lambda : '1'
+ self.destination.name = lambda : 'bar'
self.destinationParent = CalDAVResource()
- self.destinationParent.name = lambda : '2'
+ self.destinationParent.name = lambda : 'foo'
self.destinationParent.isSupportedComponent = lambda x: True
+
def _getSampleCalendar(self):
return Component.fromString("""BEGIN:VCALENDAR
VERSION:2.0
@@ -72,9 +78,13 @@
END:VCALENDAR
""")
+
def _getStorer(self, calendar):
self.sampleCalendar = calendar
req = SimpleRequest(None, "COPY", "http://example.com/foo/bar")
+ req._rememberResource(self.destination, "/foo/bar")
+ req._rememberResource(self.destinationParent, "/foo/")
+ req._rememberResource(Resource(), "/")
self.storer = StoreCalendarObjectResource(
req,
destination=self.destination,
@@ -83,7 +93,8 @@
calendar=self.sampleCalendar
)
return self.storer
-
+
+
@inlineCallbacks
def test_simpleValidRequest(self):
"""
@@ -110,7 +121,7 @@
eventComponent = list(self.sampleCalendar.subcomponents())[0]
for x in xrange(config.MaxAttendeesPerInstance):
eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x+3,)))
+ Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
@@ -143,7 +154,7 @@
eventComponent = list(self.sampleCalendar.subcomponents())[0]
for x in xrange(config.MaxAttendeesPerInstance - 5):
eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x+3,)))
+ Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
@@ -155,7 +166,7 @@
config.MaxAttendeesPerInstance -= 10
eventComponent.addProperty(
Property("ATTENDEE", "mailto:user-extra at example.com"))
-
+
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
except HTTPError, err:
@@ -187,7 +198,7 @@
eventComponent = list(self.sampleCalendar.subcomponents())[0]
for x in xrange(config.MaxAttendeesPerInstance - 5):
eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x+3,)))
+ Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
@@ -197,7 +208,7 @@
# Now reduce the limit and try to store without any additional attendees.
config.MaxAttendeesPerInstance -= 10
-
+
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
except HTTPError:
@@ -209,8 +220,8 @@
eventComponent = list(self.sampleCalendar.subcomponents())[0]
for x in xrange(config.MaxAttendeesPerInstance + 2):
eventComponent.addProperty(
- Property("ATTENDEE", "mailto:user%d at example.com" % (x+3,)))
-
+ Property("ATTENDEE", "mailto:user%d at example.com" % (x + 3,)))
+
try:
yield self._getStorer(self.sampleCalendar).fullValidation()
except HTTPError:
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/upgrade.py 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/twistedcaldav/upgrade.py 2013-04-16 22:19:02 UTC (rev 11051)
@@ -39,7 +39,9 @@
from twistedcaldav.directory import calendaruserproxy
from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
-from twistedcaldav.directory.directory import DirectoryService, GroupMembershipCacheUpdater
+from twistedcaldav.directory.directory import DirectoryService
+from twistedcaldav.directory.directory import GroupMembershipCacheUpdater
+from twistedcaldav.directory.directory import scheduleNextGroupCachingUpdate
from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
from twistedcaldav.directory.resourceinfo import ResourceInfoDatabase
from twistedcaldav.directory.xmlfile import XMLDirectoryService
@@ -67,6 +69,7 @@
from twext.python.parallel import Parallelizer
from twistedcaldav.scheduling.imip.mailgateway import migrateTokensToStore
+from twistedcaldav.scheduling.imip.inbound import scheduleNextMailPoll
deadPropertyXattrPrefix = namedAny(
@@ -1069,11 +1072,14 @@
proxydb = proxydbClass(**self.config.ProxyDBService.params)
updater = GroupMembershipCacheUpdater(proxydb,
- directory, self.config.GroupCaching.ExpireSeconds,
- self.config.GroupCaching.LockSeconds,
+ directory,
+ self.config.GroupCaching.UpdateSeconds,
+ self.config.GroupCaching.ExpireSeconds,
namespace=self.config.GroupCaching.MemcachedPool,
useExternalProxies=self.config.GroupCaching.UseExternalProxies)
yield updater.updateCache(fast=True)
+ # Set in motion the work queue based updates:
+ yield scheduleNextGroupCachingUpdate(self.store, 0)
uid, gid = getCalendarServerIDs(self.config)
dbPath = os.path.join(self.config.DataRoot, "proxies.sqlite")
@@ -1087,6 +1093,9 @@
# Migrate mail tokens from sqlite to store
yield migrateTokensToStore(self.config.DataRoot, self.store)
+ # Set mail polling in motion
+ if self.config.Scheduling.iMIP.Enabled:
+ yield scheduleNextMailPoll(self.store, 0)
Modified: CalendarServer/branches/users/glyph/sharedgroups-2/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/branches/users/glyph/sharedgroups-2/txdav/common/datastore/sql_schema/current.sql 2013-04-16 22:18:25 UTC (rev 11050)
+++ CalendarServer/branches/users/glyph/sharedgroups-2/txdav/common/datastore/sql_schema/current.sql 2013-04-16 22:19:02 UTC (rev 11051)
@@ -618,7 +618,16 @@
PUSH_ID varchar(255) not null
);
+-----------------
+-- GroupCacher --
+-----------------
+create table GROUP_CACHER_POLLING_WORK (
+ WORK_ID integer primary key default nextval('WORKITEM_SEQ') not null,
+ NOT_BEFORE timestamp default timezone('UTC', CURRENT_TIMESTAMP)
+);
+
+
--------------------
-- Schema Version --
--------------------
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130416/95957dbd/attachment-0001.html>
More information about the calendarserver-changes
mailing list