def get_square(self, location, for_update=False):
        '''Gets a map square.

        @param for_update: If you want to update this square (store its
            changes back to the database) you should set this flag.
            Note that it automatically updates the changes when
            `commit' method calls.

        @raise InvalidLocationError
        @raise LockAlreadyAquiredError
        '''
        square_id = "{0},{1}".format(*location)

        if for_update:
            self.get_lock(square_id)

        mc_connection = MemcachedConnection().get_connection()

        result = mc_connection.get(square_id)

        if result is None:
            raise exceptions.InvalidLocationError(
                "Location {0} is not valid.".format(square_id))

        if for_update:
            transaction = self._get_transaction()
            transaction.store_object(result)

        # Calling all registered hooks and letting them update the object.
        for hook in self._hooks:
            result = hook.square_got(location, result, for_update)

        return result
    def get_robot(self, robot_id, for_update=False):
        '''Gets the robot object with the specified ID from the database.

        @param robot_id: ID of the robot to get from database.
        @param for_update: If you want to update this robot (store its
            changes back to the database) you should set this flag.
            Note that it automatically updates the changes when
            `commit' method calls.

        @raise RobotNotFoundError: When no robot found with this ID.
        @raise LockAlreadyAquiredError
        '''
        if for_update:
            self.get_lock(robot_id)

        mc_connection = MemcachedConnection().get_connection()

        result = mc_connection.get(robot_id)

        if result is None:
            raise exceptions.RobotNotFoundError(
                "Robot {0} not found.".format(robot_id))

        if for_update:
            transaction = self._get_transaction()
            transaction.store_object(result)

        # Calling all registered hooks and letting them update the object.
        for hook in self._hooks:
            result = hook.robot_got(result, for_update)

        return result
    def set_world_size(self, size):
        '''Stores the world's size on database.'''
        mc_connection = MemcachedConnection().get_connection()

        result = mc_connection.set(WORLD_SIZE_KEY, size)
        if not result:
            raise exceptions.DatabaseException(
                "Couldn't store the world size on database.")
    def get_all_robot_ids(self):
        '''Gets a list of all robot IDs exists in the database.

        @note There's a little chance that some of the robots in this list
        no longer exists.
        '''
        mc_connection = MemcachedConnection().get_connection()

        return mc_connection.get("all_robots")
    def initialize(self):
        '''Initializes the database by setting required key-values.'''
        self._hooks = []

        mc = MemcachedConnection()
        mc.config_connection(Configs().get_server_database_port())

        mc_connection = mc.get_connection()
        mc_connection.add("all_robots", [])
    def add_password(self, password):
        '''Adds the specified password to the database.

        @note: It's not transactional. It will add the password directly to the database.
        '''
        mc_connection = MemcachedConnection().get_connection()

        result = mc_connection.add("{0}{1}".format(PASSWORD_PREFIX, password),
                                   1)

        if not result:
            raise exceptions.DuplicatedPasswordError()
    def get_squares(self, locations_list):
        '''Gets a list of squares.

        @locations_list: A list of strings in the form of "x,y".
        '''
        mc_connection = MemcachedConnection().get_connection()

        result = mc_connection.get_multi(locations_list)

        if result is None:
            raise exceptions.InvalidLocationError(
                "None of the specified locations is valid.")

        return result
    def add_square_row(self, row):
        '''Adds a row of squares to the map.

        @note: It's not transactional. It changes the database at realtime.

        @param row: A list of MapSqare.
        '''
        mc_connection = MemcachedConnection().get_connection()

        for square in row:
            result = mc_connection.add(square.get_id(), square)
            if not result:
                raise exceptions.DatabaseException(
                    "A square is already exists in location {0}!".format(
                        square.get_id()))
    def _add_robot_to_location(self, robot_id, location):
        '''Adds the specified robot ID to the specified location on the map.'''
        mc_connection = MemcachedConnection().get_connection()

        location_id = "{0},{1}".format(*location)
        map_square = mc_connection.get(location_id)

        if map_square is None:
            # This exception should never happen!
            raise exceptions.InvalidLocationError(
                "MapSquare object on {0} not found!".format(location))

        map_square.set_robot_id(robot_id)

        transaction = self._get_transaction()
        transaction.store_object(map_square)
    def pop_password(self, password):
        '''Removes a password from database.

        This operation is atomic, no concurrency will happen.

        @note: It's not transactional. It will pop the password directly
            from the database.

        @raise InvalidPasswordError: If password not found.
        '''
        mc_connection = MemcachedConnection().get_connection()

        password_key = "{0}{1}".format(PASSWORD_PREFIX, password)

        result = mc_connection.delete(password_key)

        if not result:
            raise exceptions.InvalidPasswordError()
    def _add_robot_to_all_list(self, robot_id):
        '''Adds a robot to the list of all robot IDs.'''
        mc_connection = MemcachedConnection().get_connection()

        # Trying seven times to set the `all_robots' object, and checking for concurrency optimistically.
        for i in range(7):
            all_robots, cas_key = mc_connection.gets("all_robots")

            all_robots.append(robot_id)

            result = mc_connection.cas("all_robots", all_robots, cas_key)

            if result:
                return

            # Waiting 20 miliseconds before next try.
            time.sleep(0.02)

        # We couldn't set it, after seven tries.
        raise exceptions.CouldNotSetValueBecauseOfConcurrencyError(
            "Could not update `all_robots' object.")
Пример #12
0
    def _commit(self):
        '''Protected commit method.'''
        connection = MemcachedConnection().get_connection()

        # Adding new objects.
        add_list = {x.get_id(): x for x in self._new_objects}
        if len(add_list) > 0:
            errors = []
            try:
                errors = connection.add_multi(add_list)
            except Exception as error:
                self._logger.error("On committing: Error when calling `add_multi': {0}'n{1}".format(
                    error, traceback.format_exc()))
                raise

            if len(errors) > 0:
                # Rolling back previously added objects.
                for added_id in add_list.keys():
                    # If object is not in the errors list, it really added. Trying to delete it.
                    if added_id not in errors:
                        result = connection.delete(added_id)

                        if not result:
                            raise DatabaseFatalError("An error occured when rolling back added objects, and "
                                                     "Server could not handle it. Database may no-longer valid.")

                raise CannotAddObjectError("Could not add these objects to the database: {0}".format(errors))

        # Updating dirty objects.
        update_list = {x.get_id(): x for x in self._stored_objects if x.is_dirty()}

        if len(update_list) > 0:
            set_errors = []
            other_error = None

            try:
                set_errors = connection.set_multi(update_list)
            except Exception as error:
                self._logger.error("On committing: Error when calling `set_multi': {0}\n{1}".format(
                    error, traceback.format_exc()))
                other_error = error

            if len(set_errors) > 0 or other_error is not None:
                if len(set_errors) > 0 and len(set_errors) != len(update_list):
                    # Some of the objects are updated on the database. We can't rollback them, because
                    # we don't know the previous state of them.
                    raise DatabaseFatalError("An error occurred in setting multi keys, and server couldn't "
                                             "handle this failure. Database may no-longer valid.")

                # Since no object is updated, we can recover by removed all the newly added objects.
                if len(add_list) > 0:
                    delete_result = connection.delete_multi(add_list.keys())

                    if not delete_result:
                        # We couldn't rollback new objects! Very bad!
                        raise DatabaseFatalError(
                            "Server couldn't rollback a failed transaction. Database may no-longer valid.")

            if other_error is not None:
                raise other_error
Пример #13
0
    def __enter__(self):
        # Starting a new database on a random port.
        port = random.randrange(11600, 11700)
        self._memcached_process = subprocess.Popen(["memcached", "-l", "127.0.0.1", "-p", str(port)])

        # Waiting for the memcached to start.
        time.sleep(0.5)

        # Forcing the world to create a new instance.
        self._original_world_instance = World._single_instance
        World._single_instance = None

        # Forcing the database to create a new instance.
        self._original_database_instance = MemcachedDatabase._single_instance
        MemcachedDatabase._single_instance = None

        # Initializing the database.
        MemcachedDatabase().initialize()

        # Configuring connection to use this new port.
        mc_connection = MemcachedConnection()
        mc_connection.config_connection(port)
Пример #14
0
    def test_concurrent_add_failure(self):
        '''Tests the behavior of Database class, when concurrent add fails.'''

        # Mocking `cas' method, making it always return False.
        def mocked_cas(*args):
            return False
        mc_connection = MemcachedConnection().get_connection()
        original_cas = mc_connection.cas
        mc_connection.cas = unittest.mock.Mock(side_effect=mocked_cas)

        try:
            new_robot = Robot("test_concurrent_add_failure_9865", "123")
            database = MemcachedDatabase()

            with self.assertRaises(CouldNotSetValueBecauseOfConcurrencyError):
                database.add_robot(new_robot, (1, 1))
                database.commit()

        finally:
            # Setting back the original cas method.
            mc_connection.cas = original_cas

        # Checking to see added robot is clearly rolled back.
        self.assertFalse(mc_connection.get(new_robot.get_id()))
Пример #15
0
 def release(self):
     '''Releases the lock.
     If the lock wasn't aquired, it will ignore it silently.
     '''
     mc_connection = MemcachedConnection().get_connection()
     mc_connection.delete(self.__lock_name)
Пример #16
0
    def aquire(self):
        '''Aquires the lock. Raises exception if lock is already aquired.'''
        mc_connection = MemcachedConnection().get_connection()

        if not mc_connection.add(self.__lock_name, 1):
            raise LockAlreadyAquiredError()
Пример #17
0
 def __exit__(self, *args):
     # Setting back everything to their original values.
     World._single_instance = self._original_world_instance
     MemcachedDatabase._single_instance = self._original_database_instance
     MemcachedConnection().config_connection(Configs().get_server_database_port())
     self._memcached_process.terminate()
 def get_world_size(self):
     '''Returns the world size.'''
     mc_connection = MemcachedConnection().get_connection()
     return mc_connection.get(WORLD_SIZE_KEY)
Пример #19
0
    def aquire(self):
        '''Aquires the lock. Raises exception if lock is already aquired.'''
        mc_connection = MemcachedConnection().get_connection()

        if not mc_connection.add(self.__lock_name, 1):
            raise LockAlreadyAquiredError()
Пример #20
0
 def release(self):
     '''Releases the lock.
     If the lock wasn't aquired, it will ignore it silently.
     '''
     mc_connection = MemcachedConnection().get_connection()
     mc_connection.delete(self.__lock_name)