def test_timeout(self): """ Trying to acquire second lock times out. """ txn1 = self.pool.connection() yield NamedLock.acquire(txn1, u"a test lock") txn2 = self.pool.connection() yield self.assertFailure(NamedLock.acquire(txn2, u"a test lock"), LockTimeout) yield txn2.abort() self.flushLoggedErrors()
def _sendAttendeeAutoReply(self): """ Auto-process the calendar option to generate automatic accept/decline status and send a reply if needed. We used to have logic to suppress attendee refreshes until after all auto-replies have been processed. We can't do that with the work queue (easily) so we are going to ignore that for now. It may not be a big deal given that the refreshes are themselves done in the queue and we only do the refresh when the last queued work item is processed. @param resource: calendar resource to process @type resource: L{CalendarObject} @param partstat: new partstat value @type partstat: C{str} """ home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) if resource is not None: try: # We need to get the UID lock for implicit processing whilst we send the auto-reply # as the Organizer processing will attempt to write out data to other attendees to # refresh them. To prevent a race we need a lock. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(resource.uid()).hexdigest(),)) # Send out a reply log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - auto-reply: %s" % (home.uid(), resource.uid(), self.partstat)) from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler scheduler = ImplicitScheduler() yield scheduler.sendAttendeeReply(self.transaction, resource) except Exception, e: log.debug("ImplicitProcessing - auto-reply exception UID: '%s', %s" % (resource.uid(), str(e))) raise except:
def _doDelayedRefresh(self, attendeesToProcess): """ Do an attendee refresh that has been delayed until after processing of the request that called it. That requires that we create a new transaction to work with. @param attendeesToProcess: list of attendees to refresh. @type attendeesToProcess: C{list} """ organizer_home = (yield self.transaction.calendarHomeWithResourceID( self.homeResourceID)) organizer_resource = (yield organizer_home.objectResourceWithID( self.resourceID)) if organizer_resource is not None: try: # We need to get the UID lock for implicit processing whilst we send the auto-reply # as the Organizer processing will attempt to write out data to other attendees to # refresh them. To prevent a race we need a lock. yield NamedLock.acquire( self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(organizer_resource.uid()).hexdigest(), )) yield self._doRefresh(organizer_resource, attendeesToProcess) except Exception, e: log.debug( "ImplicitProcessing - refresh exception UID: '{uid}', {exc}", uid=organizer_resource.uid(), exc=str(e)) raise except:
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) organizerAddress = yield calendarUserFromCalendarUserUID(home.uid(), self.transaction) organizer = organizerAddress.record.canonicalCalendarUserAddress() calendar_old = Component.fromString(self.icalendarTextOld) if self.icalendarTextOld else None calendar_new = Component.fromString(self.icalendarTextNew) if self.icalendarTextNew else None log.debug("ScheduleOrganizerWork - running for ID: {id}, UID: {uid}, organizer: {org}", id=self.workID, uid=self.icalendarUid, org=organizer) # We need to get the UID lock for implicit processing. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(self.icalendarUid).hexdigest(),)) from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler scheduler = ImplicitScheduler() yield scheduler.queuedOrganizerProcessing( self.transaction, scheduleActionFromSQL[self.scheduleAction], home, resource, self.icalendarUid, calendar_old, calendar_new, self.smartMerge ) self._dequeued() except Exception, e: log.debug("ScheduleOrganizerWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=self.icalendarUid, err=str(e)) log.debug(traceback.format_exc()) raise
def test_timeoutOnPUT(self): """ PUT gets a 503 on a lock timeout. """ # Create a fake lock txn = self.transactionUnderTest() yield NamedLock.acquire(txn, "ImplicitUIDLock:%s" % (hashlib.md5("uid1").hexdigest(),)) # PUT fails principal = yield self.actualRoot.findPrincipalForAuthID("wsanchez") request = SimpleStoreRequest( self, "PUT", "/calendars/users/wsanchez/calendar/1.ics", headers=Headers({"content-type": MimeType.fromString("text/calendar")}), authPrincipal=principal ) request.stream = MemoryStream("""BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//Apple Computer\, Inc//iCal 2.0//EN VERSION:2.0 BEGIN:VEVENT UID:uid1 DTSTART;VALUE=DATE:20020101 DTEND;VALUE=DATE:20020102 DTSTAMP:20020101T121212Z SUMMARY:New Year's Day END:VEVENT END:VCALENDAR """.replace("\n", "\r\n")) response = yield self.send(request) self.assertEqual(response.code, responsecode.SERVICE_UNAVAILABLE)
def _sendAttendeeAutoReply(self): """ Auto-process the calendar option to generate automatic accept/decline status and send a reply if needed. We used to have logic to suppress attendee refreshes until after all auto-replies have been processed. We can't do that with the work queue (easily) so we are going to ignore that for now. It may not be a big deal given that the refreshes are themselves done in the queue and we only do the refresh when the last queued work item is processed. @param resource: calendar resource to process @type resource: L{CalendarObject} @param partstat: new partstat value @type partstat: C{str} """ home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) if resource is not None: try: # We need to get the UID lock for implicit processing whilst we send the auto-reply # as the Organizer processing will attempt to write out data to other attendees to # refresh them. To prevent a race we need a lock. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(resource.uid()).hexdigest(),)) # Send out a reply log.debug("ImplicitProcessing - recipient '{recip}' processing UID: '{uid}' - auto-reply: {partstat}", recip=home.uid(), uid=resource.uid(), partstat=self.partstat) from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler scheduler = ImplicitScheduler() yield scheduler.sendAttendeeReply(self.transaction, resource) except Exception, e: log.debug("ImplicitProcessing - auto-reply exception UID: '{uid}', {ex}", uid=resource.uid(), ex=str(e)) raise except:
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) organizerAddress = yield calendarUserFromCalendarUserUID(home.uid(), self.transaction) organizer = organizerAddress.record.canonicalCalendarUserAddress() calendar_old = Component.fromString(self.icalendarTextOld) if self.icalendarTextOld else None calendar_new = Component.fromString(self.icalendarTextNew) if self.icalendarTextNew else None log.debug("ScheduleOrganizerWork - running for ID: {id}, UID: {uid}, organizer: {org}", id=self.workID, uid=self.icalendarUID, org=organizer) # We need to get the UID lock for implicit processing. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(self.icalendarUID).hexdigest(),)) from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler scheduler = ImplicitScheduler() yield scheduler.queuedOrganizerProcessing( self.transaction, scheduleActionFromSQL[self.scheduleAction], home, resource, self.icalendarUID, calendar_old, calendar_new, self.smartMerge ) self._dequeued() except Exception, e: log.debug("ScheduleOrganizerWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=self.icalendarUID, err=str(e)) log.debug(traceback.format_exc()) raise
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) attendeeAddress = yield calendarUserFromCalendarUserUID(home.uid(), self.transaction) attendee = attendeeAddress.record.canonicalCalendarUserAddress() calendar = (yield resource.componentForUser()) organizer = calendar.validOrganizerForScheduling() # Deserialize "" as None changedRids = map(lambda x: DateTime.parseText(x) if x else None, self.changedRids.split(",")) if self.changedRids else None log.debug("ScheduleReplyWork - running for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=calendar.resourceUID(), att=attendee) # We need to get the UID lock for implicit processing. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(calendar.resourceUID()).hexdigest(),)) itipmsg = iTipGenerator.generateAttendeeReply(calendar, attendee, changedRids=changedRids) # Send scheduling message and process response response = (yield self.sendToOrganizer(home, "REPLY", itipmsg, attendee, organizer)) responses, all_delivered = self.extractSchedulingResponse((response,)) if not all_delivered: changed = yield self.handleSchedulingResponse(responses, calendar, False) if changed: yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTENDEE_ITIP_UPDATE) self._dequeued() except Exception, e: # FIXME: calendar may not be set here! log.debug("ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=calendar.resourceUID(), err=str(e)) raise
def test_acquire(self): """ Acquiring a lock adds a row in that transaction. """ txn = self.pool.connection() yield NamedLock.acquire(txn, u"a test lock") rows = yield Select(From=LockSchema.NAMED_LOCK).on(txn) self.assertEquals(rows, [tuple([u"a test lock"])])
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) itipmsg = Component.fromString(self.itipMsg) organizerAddress = yield calendarUserFromCalendarUserUID(home.uid(), self.transaction) organizer = organizerAddress.record.canonicalCalendarUserAddress() log.debug( "ScheduleOrganizerSendWork - running for ID: {id}, UID: {uid}, organizer: {org}, attendee: {att}", id=self.workID, uid=self.icalendarUID, org=organizer, att=self.attendee ) # We need to get the UID lock for implicit processing. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(self.icalendarUID).hexdigest(),)) from txdav.caldav.datastore.scheduling.implicit import ImplicitScheduler scheduler = ImplicitScheduler() yield scheduler.queuedOrganizerSending( self.transaction, scheduleActionFromSQL[self.scheduleAction], home, resource, self.icalendarUID, organizer, self.attendee, itipmsg, self.noRefresh ) # Handle responses - update the actual resource in the store. Note that for a create the resource did not previously # exist and is stored as None for the work item, but the scheduler will attempt to find the new resources and use # that. We need to grab the scheduler's resource for further processing. resource = scheduler.resource if resource is not None: responses, all_delivered = self.extractSchedulingResponse(scheduler.queuedResponses) if not all_delivered: # Check for all connection failed yield self.checkTemporaryFailure(responses) # Update calendar data to reflect error status calendar = (yield resource.componentForUser()) changed = self.handleSchedulingResponse(responses, calendar, True) if changed: yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ORGANIZER_ITIP_UPDATE) self._dequeued() except Exception, e: log.debug("ScheduleOrganizerSendWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=self.icalendarUID, err=str(e)) log.debug(traceback.format_exc()) raise
def test_release(self): """ Releasing an acquired lock removes the row. """ txn = self.pool.connection() lck = yield NamedLock.acquire(txn, u"a test lock") yield lck.release() rows = yield Select(From=LockSchema.NAMED_LOCK).on(txn) self.assertEquals(rows, [])
def test_autoRelease(self): """ Committing a transaction automatically releases all of its locks. """ txn = self.pool.connection() yield NamedLock.acquire(txn, u"something") yield txn.commit() txn2 = self.pool.connection() rows = yield Select(From=LockSchema.NAMED_LOCK).on(txn2) self.assertEquals(rows, [])
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID( self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) itipmsg = Component.fromString(self.itipMsg) attendeeAddress = yield calendarUserFromCalendarUserUID( home.uid(), self.transaction) attendee = attendeeAddress.record.canonicalCalendarUserAddress() organizer = itipmsg.validOrganizerForScheduling() log.debug( "ScheduleReplyWork - running for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=itipmsg.resourceUID(), att=attendee) # We need to get the UID lock for implicit processing. yield NamedLock.acquire( self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(itipmsg.resourceUID()).hexdigest(), )) # Send scheduling message and process response response = (yield self.sendToOrganizer(home, itipmsg, attendee, organizer)) if resource is not None: responses, all_delivered = self.extractSchedulingResponse( (response, )) if not all_delivered: # Check for all connection failed yield self.checkTemporaryFailure(responses) # Update calendar data to reflect error status calendar = (yield resource.componentForUser()) changed = yield self.handleSchedulingResponse( responses, calendar, False) if changed: yield resource._setComponentInternal( calendar, internal_state=ComponentUpdateState. ATTENDEE_ITIP_UPDATE) self._dequeued() except Exception, e: # FIXME: calendar may not be set here! log.debug( "ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=itipmsg.resourceUID(), err=str(e)) raise
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID( self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) attendeeAddress = yield calendarUserFromCalendarUserUID( home.uid(), self.transaction) attendee = attendeeAddress.record.canonicalCalendarUserAddress() calendar = (yield resource.componentForUser()) organizer = calendar.validOrganizerForScheduling() # Deserialize "" as None changedRids = map( lambda x: DateTime.parseText(x) if x else None, self.changedRids.split(",")) if self.changedRids else None log.debug( "ScheduleReplyWork - running for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=calendar.resourceUID(), att=attendee) # We need to get the UID lock for implicit processing. yield NamedLock.acquire( self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(calendar.resourceUID()).hexdigest(), )) itipmsg = iTipGenerator.generateAttendeeReply( calendar, attendee, changedRids=changedRids) # Send scheduling message and process response response = (yield self.sendToOrganizer(home, "REPLY", itipmsg, attendee, organizer)) responses, all_delivered = self.extractSchedulingResponse( (response, )) if not all_delivered: changed = yield self.handleSchedulingResponse( responses, calendar, False) if changed: yield resource._setComponentInternal( calendar, internal_state=ComponentUpdateState. ATTENDEE_ITIP_UPDATE) self._dequeued() except Exception, e: # FIXME: calendar may not be set here! log.debug( "ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=calendar.resourceUID(), err=str(e)) raise
def doSchedulingViaPOST(self, originator, recipients, calendar): """ The Scheduling POST operation on an Outbox. """ self.calendar = calendar self.preProcessCalendarData() if self.logItems is not None: self.logItems["recipients"] = len(recipients) self.logItems["cl"] = str(len(str(calendar))) # We might trigger an implicit scheduling operation here that will require consistency # of data for all events with the same UID. So detect this and use a lock if calendar.resourceType() != "VFREEBUSY": uid = calendar.resourceUID() yield NamedLock.acquire(self.txn, "ImplicitUIDLock:%s" % (hashlib.md5(uid).hexdigest(),)) result = (yield self.doSchedulingDirectly("POST", originator, recipients, calendar)) returnValue(result)
def doSchedulingViaPOST(self, originator, recipients, calendar): """ The Scheduling POST operation on an Outbox. """ self.calendar = calendar yield self.preProcessCalendarData() if self.logItems is not None: self.logItems["recipients"] = len(recipients) self.logItems["cl"] = str(len(str(calendar))) # We might trigger an implicit scheduling operation here that will require consistency # of data for all events with the same UID. So detect this and use a lock if calendar.resourceType() != "VFREEBUSY": uid = calendar.resourceUID() yield NamedLock.acquire(self.txn, "ImplicitUIDLock:{}".format(hashlib.md5(uid).hexdigest(),)) result = (yield self.doSchedulingDirectly("POST", originator, recipients, calendar)) returnValue(result)
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID( self.homeResourceID)) attendeeAddress = yield calendarUserFromCalendarUserUID( home.uid(), self.transaction) attendee = attendeeAddress.record.canonicalCalendarUserAddress() calendar = Component.fromString(self.icalendarText) organizer = calendar.validOrganizerForScheduling() log.debug( "ScheduleReplyCancelWork - running for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=calendar.resourceUID(), att=attendee) # We need to get the UID lock for implicit processing. yield NamedLock.acquire( self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(calendar.resourceUID()).hexdigest(), )) itipmsg = iTipGenerator.generateAttendeeReply(calendar, attendee, force_decline=True) # Send scheduling message - no need to process response as original resource is gone yield self.sendToOrganizer(home, "CANCEL", itipmsg, attendee, organizer) self._dequeued() except Exception, e: log.debug( "ScheduleReplyCancelWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=calendar.resourceUID(), err=str(e)) raise
def _doDelayedRefresh(self, attendeesToProcess): """ Do an attendee refresh that has been delayed until after processing of the request that called it. That requires that we create a new transaction to work with. @param attendeesToProcess: list of attendees to refresh. @type attendeesToProcess: C{list} """ organizer_home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) organizer_resource = (yield organizer_home.objectResourceWithID(self.resourceID)) if organizer_resource is not None: try: # We need to get the UID lock for implicit processing whilst we send the auto-reply # as the Organizer processing will attempt to write out data to other attendees to # refresh them. To prevent a race we need a lock. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(organizer_resource.uid()).hexdigest(),)) yield self._doRefresh(organizer_resource, attendeesToProcess) except Exception, e: log.debug("ImplicitProcessing - refresh exception UID: '{uid}', {exc}", uid=organizer_resource.uid(), exc=str(e)) raise except:
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) resource = (yield home.objectResourceWithID(self.resourceID)) itipmsg = Component.fromString(self.itipMsg) attendeeAddress = yield calendarUserFromCalendarUserUID(home.uid(), self.transaction) attendee = attendeeAddress.record.canonicalCalendarUserAddress() organizer = itipmsg.validOrganizerForScheduling() log.debug("ScheduleReplyWork - running for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=itipmsg.resourceUID(), att=attendee) # We need to get the UID lock for implicit processing. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(itipmsg.resourceUID()).hexdigest(),)) # Send scheduling message and process response response = (yield self.sendToOrganizer(home, itipmsg, attendee, organizer)) if resource is not None: responses, all_delivered = self.extractSchedulingResponse((response,)) if not all_delivered: # Check for all connection failed yield self.checkTemporaryFailure(responses) # Update calendar data to reflect error status calendar = (yield resource.componentForUser()) changed = yield self.handleSchedulingResponse(responses, calendar, False) if changed: yield resource._setComponentInternal(calendar, internal_state=ComponentUpdateState.ATTENDEE_ITIP_UPDATE) self._dequeued() except Exception, e: # FIXME: calendar may not be set here! log.debug("ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=itipmsg.resourceUID(), err=str(e)) raise
def doWork(self): try: home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID)) attendeeAddress = yield calendarUserFromCalendarUserUID(home.uid(), self.transaction) attendee = attendeeAddress.record.canonicalCalendarUserAddress() calendar = Component.fromString(self.icalendarText) organizer = calendar.validOrganizerForScheduling() log.debug("ScheduleReplyCancelWork - running for ID: {id}, UID: {uid}, attendee: {att}", id=self.workID, uid=calendar.resourceUID(), att=attendee) # We need to get the UID lock for implicit processing. yield NamedLock.acquire(self.transaction, "ImplicitUIDLock:%s" % (hashlib.md5(calendar.resourceUID()).hexdigest(),)) itipmsg = iTipGenerator.generateAttendeeReply(calendar, attendee, force_decline=True) # Send scheduling message - no need to process response as original resource is gone yield self.sendToOrganizer(home, "CANCEL", itipmsg, attendee, organizer) self._dequeued() except Exception, e: log.debug("ScheduleReplyCancelWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=calendar.resourceUID(), err=str(e)) raise