Esempio n. 1
0
    def groupByUID(self, groupUID, create=True):
        """
        Return or create a record for the group UID.

        @type groupUID: C{unicode}

        @return: Deferred firing with tuple of group ID C{str}, group name
            C{unicode}, membership hash C{str}, modified timestamp, and
            extant C{boolean}
        """
        results = yield GroupsRecord.query(self, GroupsRecord.groupUID == groupUID.encode("utf-8"))
        if results:
            returnValue(results[0])
        elif create:
            savepoint = SavepointAction("groupByUID")
            yield savepoint.acquire(self)
            try:
                group = yield self.addGroup(groupUID, u"", "")
                if group is None:
                    # The record does not actually exist within the directory
                    yield savepoint.release(self)
                    returnValue(None)

            except Exception:
                yield savepoint.rollback(self)
                results = yield GroupsRecord.query(self, GroupsRecord.groupUID == groupUID.encode("utf-8"))
                returnValue(results[0] if results else None)
            else:
                yield savepoint.release(self)
                returnValue(group)
        else:
            returnValue(None)
Esempio n. 2
0
    def trylock(self, where=None):
        """
        Try to lock with a select for update no wait. If it fails, rollback to
        a savepoint and return L{False}, else return L{True}.

        @param where: SQL expression used to match the rows to lock, by default this is just an expression
            that matches the primary key of this L{Record}, but it can be used to lock multiple L{Records}
            matching the expression in one go. If it is an L{str}, then all rows will be matched.
        @type where: L{SQLExpression} or L{None}
        @return: a L{Deferred} that fires when the updates have been sent to
            the database.
        """

        if where is None:
            where = self._primaryKeyComparison(self._primaryKeyValue())
        elif isinstance(where, str):
            where = None
        savepoint = SavepointAction("Record_trylock_{}".format(self.__class__.__name__))
        yield savepoint.acquire(self.transaction)
        try:
            yield Select(
                list(self.table),
                From=self.table,
                Where=where,
                ForUpdate=True,
                NoWait=True,
            ).on(self.transaction)
        except:
            yield savepoint.rollback(self.transaction)
            returnValue(False)
        else:
            yield savepoint.release(self.transaction)
            returnValue(True)
Esempio n. 3
0
    def groupByUID(self, groupUID, create=True):
        """
        Return or create a record for the group UID.

        @type groupUID: C{unicode}

        @return: Deferred firing with tuple of group ID C{str}, group name
            C{unicode}, membership hash C{str}, modified timestamp, and
            extant C{boolean}
        """
        results = yield GroupsRecord.query(
            self, GroupsRecord.groupUID == groupUID.encode("utf-8"))
        if results:
            returnValue(results[0])
        elif create:
            savepoint = SavepointAction("groupByUID")
            yield savepoint.acquire(self)
            try:
                group = yield self.addGroup(groupUID, u"", "")
                if group is None:
                    # The record does not actually exist within the directory
                    yield savepoint.release(self)
                    returnValue(None)

            except Exception:
                yield savepoint.rollback(self)
                results = yield GroupsRecord.query(
                    self, GroupsRecord.groupUID == groupUID.encode("utf-8"))
                returnValue(results[0] if results else None)
            else:
                yield savepoint.release(self)
                returnValue(group)
        else:
            returnValue(None)
Esempio n. 4
0
    def notificationsWith(cls, txn, rid, uid, status=None, create=False):
        """
        @param uid: I'm going to assume uid is utf-8 encoded bytes
        """
        if rid is not None:
            query = cls._homeSchema.RESOURCE_ID == rid
        elif uid is not None:
            query = cls._homeSchema.OWNER_UID == uid
            if status is not None:
                query = query.And(cls._homeSchema.STATUS == status)
            else:
                statusSet = (
                    _HOME_STATUS_NORMAL,
                    _HOME_STATUS_EXTERNAL,
                )
                if txn._allowDisabled:
                    statusSet += (_HOME_STATUS_DISABLED, )
                query = query.And(cls._homeSchema.STATUS.In(statusSet))
        else:
            raise AssertionError("One of rid or uid must be set")

        results = yield Select(
            cls.homeColumns(),
            From=cls._homeSchema,
            Where=query,
        ).on(txn)

        if len(results) > 1:
            # Pick the best one in order: normal, disabled and external
            byStatus = dict([
                (result[cls.homeColumns().index(cls._homeSchema.STATUS)],
                 result) for result in results
            ])
            result = byStatus.get(_HOME_STATUS_NORMAL)
            if result is None:
                result = byStatus.get(_HOME_STATUS_DISABLED)
            if result is None:
                result = byStatus.get(_HOME_STATUS_EXTERNAL)
        elif results:
            result = results[0]
        else:
            result = None

        if result:
            # Return object that already exists in the store
            homeObject = yield cls.makeClass(txn, result)
            returnValue(homeObject)
        else:
            # Can only create when uid is specified
            if not create or uid is None:
                returnValue(None)

            # Determine if the user is local or external
            record = yield txn.directoryService().recordWithUID(
                uid.decode("utf-8"))
            if record is None:
                raise DirectoryRecordNotFoundError(
                    "Cannot create home for UID since no directory record exists: {}"
                    .format(uid))

            if status is None:
                createStatus = _HOME_STATUS_NORMAL if record.thisServer(
                ) else _HOME_STATUS_EXTERNAL
            elif status == _HOME_STATUS_MIGRATING:
                if record.thisServer():
                    raise RecordNotAllowedError(
                        "Cannot migrate a user data for a user already hosted on this server"
                    )
                createStatus = status
            elif status in (
                    _HOME_STATUS_NORMAL,
                    _HOME_STATUS_EXTERNAL,
            ):
                createStatus = status
            else:
                raise RecordNotAllowedError(
                    "Cannot create home with status {}: {}".format(
                        status, uid))

            # Use savepoint so we can do a partial rollback if there is a race
            # condition where this row has already been inserted
            savepoint = SavepointAction("notificationsWithUID")
            yield savepoint.acquire(txn)

            try:
                resourceid = (yield Insert(
                    {
                        cls._homeSchema.OWNER_UID: uid,
                        cls._homeSchema.STATUS: createStatus,
                    },
                    Return=cls._homeSchema.RESOURCE_ID).on(txn))[0][0]
            except Exception:
                # FIXME: Really want to trap the pg.DatabaseError but in a non-
                # DB specific manner
                yield savepoint.rollback(txn)

                # Retry the query - row may exist now, if not re-raise
                results = yield Select(
                    cls.homeColumns(),
                    From=cls._homeSchema,
                    Where=query,
                ).on(txn)
                if results:
                    homeObject = yield cls.makeClass(txn, results[0])
                    returnValue(homeObject)
                else:
                    raise
            else:
                yield savepoint.release(txn)

                # Note that we must not cache the owner_uid->resource_id
                # mapping in the query cacher when creating as we don't want that to appear
                # until AFTER the commit
                results = yield Select(
                    cls.homeColumns(),
                    From=cls._homeSchema,
                    Where=cls._homeSchema.RESOURCE_ID == resourceid,
                ).on(txn)
                homeObject = yield cls.makeClass(txn, results[0])
                if homeObject.normal():
                    yield homeObject._initSyncToken()
                    yield homeObject.notifyChanged()
                returnValue(homeObject)
    def notificationsWith(cls, txn, rid, uid, status=None, create=False):
        """
        @param uid: I'm going to assume uid is utf-8 encoded bytes
        """
        if rid is not None:
            query = cls._homeSchema.RESOURCE_ID == rid
        elif uid is not None:
            query = cls._homeSchema.OWNER_UID == uid
            if status is not None:
                query = query.And(cls._homeSchema.STATUS == status)
            else:
                statusSet = (_HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL,)
                if txn._allowDisabled:
                    statusSet += (_HOME_STATUS_DISABLED,)
                query = query.And(cls._homeSchema.STATUS.In(statusSet))
        else:
            raise AssertionError("One of rid or uid must be set")

        results = yield Select(
            cls.homeColumns(),
            From=cls._homeSchema,
            Where=query,
        ).on(txn)

        if len(results) > 1:
            # Pick the best one in order: normal, disabled and external
            byStatus = dict([(result[cls.homeColumns().index(cls._homeSchema.STATUS)], result) for result in results])
            result = byStatus.get(_HOME_STATUS_NORMAL)
            if result is None:
                result = byStatus.get(_HOME_STATUS_DISABLED)
            if result is None:
                result = byStatus.get(_HOME_STATUS_EXTERNAL)
        elif results:
            result = results[0]
        else:
            result = None

        if result:
            # Return object that already exists in the store
            homeObject = yield cls.makeClass(txn, result)
            returnValue(homeObject)
        else:
            # Can only create when uid is specified
            if not create or uid is None:
                returnValue(None)

            # Determine if the user is local or external
            record = yield txn.directoryService().recordWithUID(uid.decode("utf-8"))
            if record is None:
                raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".format(uid))

            if status is None:
                createStatus = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL
            elif status == _HOME_STATUS_MIGRATING:
                if record.thisServer():
                    raise RecordNotAllowedError("Cannot migrate a user data for a user already hosted on this server")
                createStatus = status
            elif status in (_HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL,):
                createStatus = status
            else:
                raise RecordNotAllowedError("Cannot create home with status {}: {}".format(status, uid))

            # Use savepoint so we can do a partial rollback if there is a race
            # condition where this row has already been inserted
            savepoint = SavepointAction("notificationsWithUID")
            yield savepoint.acquire(txn)

            try:
                resourceid = (yield Insert(
                    {
                        cls._homeSchema.OWNER_UID: uid,
                        cls._homeSchema.STATUS: createStatus,
                    },
                    Return=cls._homeSchema.RESOURCE_ID
                ).on(txn))[0][0]
            except Exception:
                # FIXME: Really want to trap the pg.DatabaseError but in a non-
                # DB specific manner
                yield savepoint.rollback(txn)

                # Retry the query - row may exist now, if not re-raise
                results = yield Select(
                    cls.homeColumns(),
                    From=cls._homeSchema,
                    Where=query,
                ).on(txn)
                if results:
                    homeObject = yield cls.makeClass(txn, results[0])
                    returnValue(homeObject)
                else:
                    raise
            else:
                yield savepoint.release(txn)

                # Note that we must not cache the owner_uid->resource_id
                # mapping in the query cacher when creating as we don't want that to appear
                # until AFTER the commit
                results = yield Select(
                    cls.homeColumns(),
                    From=cls._homeSchema,
                    Where=cls._homeSchema.RESOURCE_ID == resourceid,
                ).on(txn)
                homeObject = yield cls.makeClass(txn, results[0])
                if homeObject.normal():
                    yield homeObject._initSyncToken()
                    yield homeObject.notifyChanged()
                returnValue(homeObject)