コード例 #1
0
class ExtendableRedlockTest(unittest.TestCase):
    @patch('redis.StrictRedis', new=FakeRedisCustom)
    def setUp(self):
        self.redlock = ExtendableRedlock(get_servers_pool(active=1, inactive=0))
        self.redlock_with_51_servers_up_49_down = ExtendableRedlock(get_servers_pool(active=51, inactive=49))
        self.redlock_with_50_servers_up_50_down = ExtendableRedlock(get_servers_pool(active=50, inactive=50))

    def tearDown(self):
        fakeredis.DATABASES = {}

    def test_bad_connection_info(self):
        with self.assertRaises(Warning):
            Redlock([{"cat": "hog"}])

    def test_should_be_able_to_lock_a_resource_after_it_has_been_unlocked(self):
        lock = self.redlock_with_51_servers_up_49_down.lock("shorts", 10)
        self.assertIsInstance(lock, Lock)
        self.redlock_with_51_servers_up_49_down.unlock(lock)
        lock = self.redlock_with_51_servers_up_49_down.lock("shorts", 10)
        self.assertIsInstance(lock, Lock)

    def test_safety_property_mutual_exclusion(self):
        """
            At any given moment, only one client can hold a lock.
        """
        lock = self.redlock_with_51_servers_up_49_down.lock("shorts", 100000)
        self.assertIsInstance(lock, Lock)
        bad = self.redlock_with_51_servers_up_49_down.lock("shorts", 10)
        self.assertFalse(bad)

    def test_liveness_property_A_deadlocks_free(self):
        """
            Eventually it is always possible to acquire a lock,
            even if the client that locked a resource crashed or gets partitioned.
        """
        lock_A = self.redlock_with_51_servers_up_49_down.lock("shorts", 500)
        self.assertIsInstance(lock_A, Lock)
        sleep(1)
        lock_B = self.redlock_with_51_servers_up_49_down.lock("shorts", 1000)
        self.assertIsInstance(lock_B, Lock)

    def test_liveness_property_B_fault_tolerance(self):
        """
            As long as the majority of Redis nodes are up, clients are able to acquire and release locks.
        """
        lock_with_majority = self.redlock_with_51_servers_up_49_down.lock("shorts", 100000)
        self.assertIsInstance(lock_with_majority, Lock)

        lock_without_majority = self.redlock_with_50_servers_up_50_down.lock("shorts", 100000)
        self.assertEqual(lock_without_majority, False)

    def test_locks_are_released_when_majority_is_not_reached(self):
        """
            [...] clients that fail to acquire the majority of locks,
            to release the (partially) acquired locks ASAP [...]
        """
        lock = self.redlock_with_50_servers_up_50_down.lock("shorts", 10000)
        self.assertEqual(lock, False)

        for server in self.redlock_with_50_servers_up_50_down.servers:
            self.assertEqual(server.get('shorts'), None)

    def test_avoid_removing_locks_created_by_other_clients(self):
        """
            [...] avoid removing a lock that was created by another client.
        """
        lock_A = self.redlock.lock("shorts", 100000)
        self.assertIsInstance(lock_A, Lock)

        lock_B = Lock(validity=9000, resource='shorts', key='abcde')
        self.redlock.unlock(lock_B)

        for server in self.redlock.servers:
            self.assertEqual(server.get('shorts'), lock_A.key)

    def test_two_at_the_same_time_only_one_gets_it(self):
        threads = []
        threads_that_got_the_lock = []

        def get_lock_and_register(thread_name, redlock, resource, output):
            lock = redlock.lock(resource, 100000)
            if lock:
                output.append(thread_name)

        for i in range(2):
            thread = threading.Thread(
                target=get_lock_and_register, args=(i, self.redlock, 'shorts', threads_that_got_the_lock)
            )
            thread.start()
            threads.append(thread)

        for t in threads:
            t.join()

        self.assertEqual(len(threads_that_got_the_lock), 1)

    def test_a_lock_can_be_extended(self):
        lock = self.redlock_with_51_servers_up_49_down.lock("shorts", 500)

        extended = self.redlock_with_51_servers_up_49_down.extend(lock, 1000)
        self.assertEqual(extended, True)

        sleep(0.6)
        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock), is_(True))

    def test_a_lock_cannot_be_extended_if_it_is_expired(self):
        lock = self.redlock_with_51_servers_up_49_down.lock("shorts", 500)
        sleep(0.75)
        extended = self.redlock_with_51_servers_up_49_down.extend(lock, 1000)

        self.assertEqual(extended, False)
        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock), is_(False))

    def test_locks_have_a_close_to_ttl_validity(self):
        requested_ttl_ms = 10000
        how_close_ms = 500
        lock_A = self.redlock.lock('pants', requested_ttl_ms)
        self.assertIsInstance(lock_A, Lock)

        self.assertGreaterEqual(lock_A.validity, requested_ttl_ms - how_close_ms)

    def test_a_lock_is_always_valid_within_its_validity_time(self):
        lock = self.redlock.lock('test_regular_lock', seconds_to_ms(30))
        assert_that(self.redlock.is_valid(lock), is_(True))

    def test_a_lock_goes_invalid_after_validity_time(self):
        lock = self.redlock_with_51_servers_up_49_down.lock('test_goes_invalid', 350)
        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock), is_(True))

        sleep(ms_to_seconds(lock.validity + 100))
        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock), is_(False))

    def test_a_lock_goes_invalid_if_majority_not_attained(self):
        lock = self.redlock_with_51_servers_up_49_down.lock('test_goes_invalid', seconds_to_ms(30))
        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock), is_(True))

        twenty_five_servers_where_lock_is_valid = [s for s in self.redlock_with_51_servers_up_49_down.servers if
                                                   s.get(lock.resource) == lock.key][:25]

        for server in twenty_five_servers_where_lock_is_valid:
            server.delete(lock.resource)

        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock), is_(False))

    def test_autoextend_automatically_extends_the_lock_expiry(self):
        lock = self.redlock_with_51_servers_up_49_down.lock('test_autoextend', 500)

        with self.redlock_with_51_servers_up_49_down.autoextend(lock, every_ms=200, new_ttl=500):
            sleep(1)

            assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock), is_(True))

    def test_autoextend_automatically_extends_the_lock_expiry_explicit_start_stop(self):
        lock = self.redlock_with_51_servers_up_49_down.lock('test_autoextend', 500)
        try:
            self.redlock_with_51_servers_up_49_down.start_autoextend(lock, every_ms=200, new_ttl=500)
            sleep(1)

            assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock), is_(True))
        finally:
            self.redlock_with_51_servers_up_49_down.stop_autoextend(lock)

    def test_autoextending_lock_unable_to_renew(self):
        lock = self.redlock_with_51_servers_up_49_down.lock('test_unable_to_renew', 500)
        with self.redlock_with_51_servers_up_49_down.autoextend(lock, every_ms=100, new_ttl=500):
            for server in self.redlock.servers:
                server.flushall()

            sleep(1)

            assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock), is_(False))

    def test_autoextend_is_not_valid_if_not_refreshed_fast_enough(self):
        lock = self.redlock.lock('test_should_raise', 150)
        with self.redlock_with_51_servers_up_49_down.autoextend(lock, every_ms=250, new_ttl=150):
            sleep(1)
            assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock), is_(False))

    def test_autoextend_twice_is_an_error(self):
        lock = self.redlock_with_51_servers_up_49_down.lock('test_autoextend', 500)

        with self.redlock_with_51_servers_up_49_down.autoextend(lock, every_ms=200, new_ttl=500):
            with self.assertRaises(LockAutoextendAlreadyRunning):
                self.redlock_with_51_servers_up_49_down.start_autoextend(lock, every_ms=200, new_ttl=500)

    def test_autoextend_start_stop_start_stop(self):
        lock = self.redlock_with_51_servers_up_49_down.lock('test_autoextend', 500)
        with self.redlock_with_51_servers_up_49_down.autoextend(lock, every_ms=200, new_ttl=500):
            pass

        with self.redlock_with_51_servers_up_49_down.autoextend(lock, every_ms=200, new_ttl=500):
            pass
コード例 #2
0
class ExtendableRedlockTest(unittest.TestCase):
    @patch('redis.StrictRedis', new=FakeRedisCustom)
    def setUp(self):
        self.redlock = ExtendableRedlock(get_servers_pool(active=1,
                                                          inactive=0))
        self.redlock_with_51_servers_up_49_down = ExtendableRedlock(
            get_servers_pool(active=51, inactive=49))
        self.redlock_with_50_servers_up_50_down = ExtendableRedlock(
            get_servers_pool(active=50, inactive=50))

    def tearDown(self):
        fakeredis.DATABASES = {}

    def test_bad_connection_info(self):
        with self.assertRaises(Warning):
            Redlock([{"cat": "hog"}])

    def test_should_be_able_to_lock_a_resource_after_it_has_been_unlocked(
            self):
        lock = self.redlock_with_51_servers_up_49_down.lock("shorts", 10)
        self.assertIsInstance(lock, Lock)
        self.redlock_with_51_servers_up_49_down.unlock(lock)
        lock = self.redlock_with_51_servers_up_49_down.lock("shorts", 10)
        self.assertIsInstance(lock, Lock)

    def test_safety_property_mutual_exclusion(self):
        """
            At any given moment, only one client can hold a lock.
        """
        lock = self.redlock_with_51_servers_up_49_down.lock("shorts", 100000)
        self.assertIsInstance(lock, Lock)
        bad = self.redlock_with_51_servers_up_49_down.lock("shorts", 10)
        self.assertFalse(bad)

    def test_liveness_property_A_deadlocks_free(self):
        """
            Eventually it is always possible to acquire a lock,
            even if the client that locked a resource crashed or gets partitioned.
        """
        lock_A = self.redlock_with_51_servers_up_49_down.lock("shorts", 500)
        self.assertIsInstance(lock_A, Lock)
        sleep(1)
        lock_B = self.redlock_with_51_servers_up_49_down.lock("shorts", 1000)
        self.assertIsInstance(lock_B, Lock)

    def test_liveness_property_B_fault_tolerance(self):
        """
            As long as the majority of Redis nodes are up, clients are able to acquire and release locks.
        """
        lock_with_majority = self.redlock_with_51_servers_up_49_down.lock(
            "shorts", 100000)
        self.assertIsInstance(lock_with_majority, Lock)

        lock_without_majority = self.redlock_with_50_servers_up_50_down.lock(
            "shorts", 100000)
        self.assertEqual(lock_without_majority, False)

    def test_locks_are_released_when_majority_is_not_reached(self):
        """
            [...] clients that fail to acquire the majority of locks,
            to release the (partially) acquired locks ASAP [...]
        """
        lock = self.redlock_with_50_servers_up_50_down.lock("shorts", 10000)
        self.assertEqual(lock, False)

        for server in self.redlock_with_50_servers_up_50_down.servers:
            self.assertEqual(server.get('shorts'), None)

    def test_avoid_removing_locks_created_by_other_clients(self):
        """
            [...] avoid removing a lock that was created by another client.
        """
        lock_A = self.redlock.lock("shorts", 100000)
        self.assertIsInstance(lock_A, Lock)

        lock_B = Lock(validity=9000, resource='shorts', key='abcde')
        self.redlock.unlock(lock_B)

        for server in self.redlock.servers:
            self.assertEqual(server.get('shorts'), lock_A.key)

    def test_two_at_the_same_time_only_one_gets_it(self):
        threads = []
        threads_that_got_the_lock = []

        def get_lock_and_register(thread_name, redlock, resource, output):
            lock = redlock.lock(resource, 100000)
            if lock:
                output.append(thread_name)

        for i in range(2):
            thread = threading.Thread(target=get_lock_and_register,
                                      args=(i, self.redlock, 'shorts',
                                            threads_that_got_the_lock))
            thread.start()
            threads.append(thread)

        for t in threads:
            t.join()

        self.assertEqual(len(threads_that_got_the_lock), 1)

    def test_a_lock_can_be_extended(self):
        lock = self.redlock_with_51_servers_up_49_down.lock("shorts", 500)

        extended = self.redlock_with_51_servers_up_49_down.extend(lock, 1000)
        self.assertEqual(extended, True)

        sleep(0.6)
        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock),
                    is_(True))

    def test_a_lock_cannot_be_extended_if_it_is_expired(self):
        lock = self.redlock_with_51_servers_up_49_down.lock("shorts", 500)
        sleep(0.75)
        extended = self.redlock_with_51_servers_up_49_down.extend(lock, 1000)

        self.assertEqual(extended, False)
        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock),
                    is_(False))

    def test_locks_have_a_close_to_ttl_validity(self):
        requested_ttl_ms = 10000
        how_close_ms = 500
        lock_A = self.redlock.lock('pants', requested_ttl_ms)
        self.assertIsInstance(lock_A, Lock)

        self.assertGreaterEqual(lock_A.validity,
                                requested_ttl_ms - how_close_ms)

    def test_a_lock_is_always_valid_within_its_validity_time(self):
        lock = self.redlock.lock('test_regular_lock', seconds_to_ms(30))
        assert_that(self.redlock.is_valid(lock), is_(True))

    def test_a_lock_goes_invalid_after_validity_time(self):
        lock = self.redlock_with_51_servers_up_49_down.lock(
            'test_goes_invalid', 350)
        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock),
                    is_(True))

        sleep(ms_to_seconds(lock.validity + 100))
        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock),
                    is_(False))

    def test_a_lock_goes_invalid_if_majority_not_attained(self):
        lock = self.redlock_with_51_servers_up_49_down.lock(
            'test_goes_invalid', seconds_to_ms(30))
        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock),
                    is_(True))

        twenty_five_servers_where_lock_is_valid = [
            s for s in self.redlock_with_51_servers_up_49_down.servers
            if s.get(lock.resource) == lock.key
        ][:25]

        for server in twenty_five_servers_where_lock_is_valid:
            server.delete(lock.resource)

        assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock),
                    is_(False))

    def test_autoextend_automatically_extends_the_lock_expiry(self):
        lock = self.redlock_with_51_servers_up_49_down.lock(
            'test_autoextend', 500)

        with self.redlock_with_51_servers_up_49_down.autoextend(lock,
                                                                every_ms=200,
                                                                new_ttl=500):
            sleep(1)

            assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock),
                        is_(True))

    def test_autoextend_automatically_extends_the_lock_expiry_explicit_start_stop(
            self):
        lock = self.redlock_with_51_servers_up_49_down.lock(
            'test_autoextend', 500)
        try:
            self.redlock_with_51_servers_up_49_down.start_autoextend(
                lock, every_ms=200, new_ttl=500)
            sleep(1)

            assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock),
                        is_(True))
        finally:
            self.redlock_with_51_servers_up_49_down.stop_autoextend(lock)

    def test_autoextending_lock_unable_to_renew(self):
        lock = self.redlock_with_51_servers_up_49_down.lock(
            'test_unable_to_renew', 500)
        with self.redlock_with_51_servers_up_49_down.autoextend(lock,
                                                                every_ms=100,
                                                                new_ttl=500):
            for server in self.redlock.servers:
                server.flushall()

            sleep(1)

            assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock),
                        is_(False))

    def test_autoextend_is_not_valid_if_not_refreshed_fast_enough(self):
        lock = self.redlock.lock('test_should_raise', 150)
        with self.redlock_with_51_servers_up_49_down.autoextend(lock,
                                                                every_ms=250,
                                                                new_ttl=150):
            sleep(1)
            assert_that(self.redlock_with_51_servers_up_49_down.is_valid(lock),
                        is_(False))

    def test_autoextend_twice_is_an_error(self):
        lock = self.redlock_with_51_servers_up_49_down.lock(
            'test_autoextend', 500)

        with self.redlock_with_51_servers_up_49_down.autoextend(lock,
                                                                every_ms=200,
                                                                new_ttl=500):
            with self.assertRaises(LockAutoextendAlreadyRunning):
                self.redlock_with_51_servers_up_49_down.start_autoextend(
                    lock, every_ms=200, new_ttl=500)

    def test_autoextend_start_stop_start_stop(self):
        lock = self.redlock_with_51_servers_up_49_down.lock(
            'test_autoextend', 500)
        with self.redlock_with_51_servers_up_49_down.autoextend(lock,
                                                                every_ms=200,
                                                                new_ttl=500):
            pass

        with self.redlock_with_51_servers_up_49_down.autoextend(lock,
                                                                every_ms=200,
                                                                new_ttl=500):
            pass