diff options
| -rw-r--r-- | requirements.txt | 4 | ||||
| -rw-r--r-- | tooz/coordination.py | 3 | ||||
| -rw-r--r-- | tooz/drivers/etcd.py | 2 | ||||
| -rw-r--r-- | tooz/tests/__init__.py | 5 | ||||
| -rw-r--r-- | tooz/tests/drivers/test_file.py | 5 | ||||
| -rw-r--r-- | tooz/tests/test_coordination.py | 99 | ||||
| -rw-r--r-- | tooz/tests/test_etcd.py | 44 | ||||
| -rw-r--r-- | tooz/tests/test_memcache.py | 16 | ||||
| -rw-r--r-- | tooz/tests/test_mysql.py | 5 | ||||
| -rw-r--r-- | tooz/tests/test_postgresql.py | 5 | ||||
| -rw-r--r-- | tox.ini | 2 |
11 files changed, 124 insertions, 66 deletions
diff --git a/requirements.txt b/requirements.txt index 9da24d1..0513da4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=1.6 # Apache-2.0 -stevedore>=1.10.0 # Apache-2.0 +stevedore>=1.16.0 # Apache-2.0 six>=1.9.0 # MIT enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD voluptuous>=0.8.9 # BSD License @@ -10,6 +10,6 @@ msgpack-python>=0.4.0 # Apache-2.0 fasteners>=0.7 # Apache-2.0 retrying!=1.3.0,>=1.2.3 # Apache-2.0 futures>=3.0;python_version=='2.7' or python_version=='2.6' # BSD -futurist>=0.11.0 # Apache-2.0 +futurist!=0.15.0,>=0.11.0 # Apache-2.0 oslo.utils>=3.15.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 diff --git a/tooz/coordination.py b/tooz/coordination.py index 4307684..fdc80a0 100644 --- a/tooz/coordination.py +++ b/tooz/coordination.py @@ -322,6 +322,9 @@ class CoordinationDriver(object): group_id in self._group_members): del self._group_members[group_id] + if not self._hooks_leave_group[group_id]: + del self._hooks_leave_group[group_id] + @abc.abstractmethod def watch_elected_as_leader(self, group_id, callback): """Call a function when member gets elected as leader. diff --git a/tooz/drivers/etcd.py b/tooz/drivers/etcd.py index 9d6531e..bdfa35c 100644 --- a/tooz/drivers/etcd.py +++ b/tooz/drivers/etcd.py @@ -142,7 +142,7 @@ class EtcdLock(locking.Lock): try: reply = self.client.get( self._lock_url + - "?wait=true&waitIndex=%d" % reply['index'], + "?wait=true&waitIndex=%d" % (reply['index'] + 1), make_url=False, timeout=watch.leftover() if watch else None) except requests.exceptions.RequestException: diff --git a/tooz/tests/__init__.py b/tooz/tests/__init__.py index dde33f3..f7cd3b6 100644 --- a/tooz/tests/__init__.py +++ b/tooz/tests/__init__.py @@ -15,6 +15,7 @@ # under the License. import functools +import uuid import six from testtools import testcase @@ -22,6 +23,10 @@ from testtools import testcase import tooz +def get_random_uuid(): + return str(uuid.uuid4()).encode('ascii') + + def _skip_decorator(func): @functools.wraps(func) def skip_if_not_implemented(*args, **kwargs): diff --git a/tooz/tests/drivers/test_file.py b/tooz/tests/drivers/test_file.py index 692e68b..c95eb66 100644 --- a/tooz/tests/drivers/test_file.py +++ b/tooz/tests/drivers/test_file.py @@ -15,16 +15,15 @@ # License for the specific language governing permissions and limitations # under the License. -import uuid - import mock from testtools import testcase from tooz import coordination +from tooz import tests class TestFileDriver(testcase.TestCase): - _FAKE_MEMBER_ID = str(uuid.uuid4()).encode('ascii') + _FAKE_MEMBER_ID = tests.get_random_uuid() def test_base_dir(self): file_path = '/fake/file/path' diff --git a/tooz/tests/test_coordination.py b/tooz/tests/test_coordination.py index 5e22aeb..c23a790 100644 --- a/tooz/tests/test_coordination.py +++ b/tooz/tests/test_coordination.py @@ -17,7 +17,6 @@ import os import threading import time -import uuid from concurrent import futures import fixtures @@ -55,8 +54,8 @@ class TestAPI(tests.TestCaseSkipNotImplemented): if self.url is None: self.skipTest("No URL set for this driver") self.useFixture(fixtures.NestedTempfile()) - self.group_id = self._get_random_uuid() - self.member_id = self._get_random_uuid() + self.group_id = tests.get_random_uuid() + self.member_id = tests.get_random_uuid() self._coord = tooz.coordination.get_coordinator(self.url, self.member_id) self._coord.start() @@ -88,8 +87,8 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self.assertTrue(self.group_id in all_group_ids) def test_get_lock_release_broken(self): - name = self._get_random_uuid() - memberid2 = self._get_random_uuid() + name = tests.get_random_uuid() + memberid2 = tests.get_random_uuid() coord2 = tooz.coordination.get_coordinator(self.url, memberid2) coord2.start() @@ -101,7 +100,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self.assertTrue(lock2.acquire(blocking=False)) self.assertFalse(lock1.release()) # Assert lock is not accidentally broken now - memberid3 = self._get_random_uuid() + memberid3 = tests.get_random_uuid() coord3 = tooz.coordination.get_coordinator(self.url, memberid3) coord3.start() @@ -115,7 +114,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): create_group.get) def test_get_groups(self): - groups_ids = [self._get_random_uuid() for _ in range(0, 5)] + groups_ids = [tests.get_random_uuid() for _ in range(0, 5)] for group_id in groups_ids: self._coord.create_group(group_id).get() created_groups = self._coord.get_groups().get() @@ -200,7 +199,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): leave_group.get) def test_get_lock_twice_locked_one_released_two(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock1 = self._coord.get_lock(name) lock2 = self._coord.get_lock(name) self.assertTrue(lock1.acquire()) @@ -210,8 +209,8 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self.assertFalse(lock2.release()) def test_get_members(self): - group_id_test2 = self._get_random_uuid() - member_id_test2 = self._get_random_uuid() + group_id_test2 = tests.get_random_uuid() + member_id_test2 = tests.get_random_uuid() client2 = tooz.coordination.get_coordinator(self.url, member_id_test2) client2.start() @@ -290,7 +289,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): def test_get_member_info_nonjoined_member(self): self._coord.create_group(self.group_id).get() - member_id = self._get_random_uuid() + member_id = tests.get_random_uuid() member_info = self._coord.get_member_info(self.group_id, member_id) self.assertRaises(tooz.coordination.MemberNotJoined, @@ -342,7 +341,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): heart.wait() def test_disconnect_leave_group(self): - member_id_test2 = self._get_random_uuid() + member_id_test2 = tests.get_random_uuid() client2 = tooz.coordination.get_coordinator(self.url, member_id_test2) client2.start() @@ -370,7 +369,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self._coord = tooz.coordination.get_coordinator(url, self.member_id) self._coord.start() - member_id_test2 = self._get_random_uuid() + member_id_test2 = tests.get_random_uuid() client2 = tooz.coordination.get_coordinator(url, member_id_test2) client2.start() self._coord.create_group(self.group_id).get() @@ -409,7 +408,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): return 42 def test_watch_group_join(self): - member_id_test2 = self._get_random_uuid() + member_id_test2 = tests.get_random_uuid() client2 = tooz.coordination.get_coordinator(self.url, member_id_test2) client2.start() @@ -443,7 +442,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self.assertIsNone(self.event) def test_watch_leave_group(self): - member_id_test2 = self._get_random_uuid() + member_id_test2 = tests.get_random_uuid() client2 = tooz.coordination.get_coordinator(self.url, member_id_test2) client2.start() @@ -494,7 +493,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self._coord.watch_join_group(self.group_id, self._set_event) self._coord.watch_leave_group(self.group_id, self._set_event) - member_id_test2 = self._get_random_uuid() + member_id_test2 = tests.get_random_uuid() client2 = tooz.coordination.get_coordinator(self.url, member_id_test2) client2.start() @@ -530,7 +529,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self._coord.watch_join_group(self.group_id, self._set_event) self._coord.watch_leave_group(self.group_id, self._set_event) - member_id_test2 = self._get_random_uuid() + member_id_test2 = tests.get_random_uuid() client2 = tooz.coordination.get_coordinator(self.url, member_id_test2) client2.start() @@ -576,7 +575,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self._coord.watch_elected_as_leader(self.group_id, self._set_event) self._coord.run_watchers() - member_id_test2 = self._get_random_uuid() + member_id_test2 = tests.get_random_uuid() client2 = tooz.coordination.get_coordinator(self.url, member_id_test2) client2.start() @@ -632,7 +631,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self._coord.watch_elected_as_leader(self.group_id, self._set_event) self._coord.run_watchers() - member_id_test2 = self._get_random_uuid() + member_id_test2 = tests.get_random_uuid() client2 = tooz.coordination.get_coordinator(self.url, member_id_test2) client2.start() @@ -676,7 +675,8 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self._coord.watch_elected_as_leader(self.group_id, self._set_event) # Ensure exactly one leader election hook exists - self.assertEqual(1, len(self._coord._hooks_elected_leader)) + self.assertEqual(1, + len(self._coord._hooks_elected_leader[self.group_id])) # Unwatch, and ensure no leader election hooks exist self._coord.unwatch_elected_as_leader(self.group_id, self._set_event) @@ -694,6 +694,19 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self._coord.unwatch_join_group, self.group_id, lambda x: None) + def test_unwatch_leave_group(self): + # Create a group and add a leave_group callback + self._coord.create_group(self.group_id).get() + self.assertEqual(0, len(self._coord._hooks_leave_group)) + self._coord.watch_leave_group(self.group_id, self._set_event) + + # Ensure exactly one leave group hook exists + self.assertEqual(1, len(self._coord._hooks_leave_group[self.group_id])) + + # Unwatch, and ensure no leave group hooks exist + self._coord.unwatch_leave_group(self.group_id, self._set_event) + self.assertEqual(0, len(self._coord._hooks_leave_group)) + def test_unwatch_leave_group_callback_not_found(self): self._coord.create_group(self.group_id).get() self.assertRaises(tooz.coordination.WatchCallbackNotFound, @@ -701,14 +714,14 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self.group_id, lambda x: None) def test_get_lock(self): - lock = self._coord.get_lock(self._get_random_uuid()) + lock = self._coord.get_lock(tests.get_random_uuid()) self.assertTrue(lock.acquire()) self.assertTrue(lock.release()) with lock: pass def test_get_lock_concurrency_locking_same_lock(self): - lock = self._coord.get_lock(self._get_random_uuid()) + lock = self._coord.get_lock(tests.get_random_uuid()) graceful_ending = threading.Event() @@ -729,13 +742,13 @@ class TestAPI(tests.TestCaseSkipNotImplemented): def _do_test_get_lock_concurrency_locking_two_lock(self, executor, use_same_coord): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock1 = self._coord.get_lock(name) with lock1: with executor(max_workers=1) as e: coord = self._coord if use_same_coord else None f = e.submit(try_to_lock_job, name, coord, self.url, - self._get_random_uuid()) + tests.get_random_uuid()) self.assertFalse(f.result()) def test_get_lock_concurrency_locking_two_lock_process(self): @@ -754,8 +767,8 @@ class TestAPI(tests.TestCaseSkipNotImplemented): # NOTE(sileht): some database based lock can have only # one lock per connection, this test ensures acquiring a # second lock doesn't release the first one. - lock1 = self._coord.get_lock(self._get_random_uuid()) - lock2 = self._coord.get_lock(self._get_random_uuid()) + lock1 = self._coord.get_lock(tests.get_random_uuid()) + lock2 = self._coord.get_lock(tests.get_random_uuid()) graceful_ending = threading.Event() thread_locked = threading.Event() @@ -781,14 +794,14 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self.assertTrue(graceful_ending.is_set()) def test_get_lock_twice_locked_twice(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock1 = self._coord.get_lock(name) lock2 = self._coord.get_lock(name) with lock1: self.assertFalse(lock2.acquire(blocking=False)) def test_get_lock_context_fails(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock1 = self._coord.get_lock(name) lock2 = self._coord.get_lock(name) with mock.patch.object(lock2, 'acquire', return_value=False): @@ -798,34 +811,34 @@ class TestAPI(tests.TestCaseSkipNotImplemented): lock2.__enter__) def test_get_lock_context_check_value(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock = self._coord.get_lock(name) with lock as returned_lock: self.assertEqual(lock, returned_lock) def test_lock_context_manager_acquire_no_argument(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock1 = self._coord.get_lock(name) lock2 = self._coord.get_lock(name) with lock1(): self.assertFalse(lock2.acquire(blocking=False)) def test_lock_context_manager_acquire_argument_return_value(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() blocking_value = 10.12 lock = self._coord.get_lock(name) with lock(blocking_value) as returned_lock: self.assertEqual(lock, returned_lock) def test_lock_context_manager_acquire_argument_release_within(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() blocking_value = 10.12 lock = self._coord.get_lock(name) with lock(blocking_value) as returned_lock: self.assertTrue(returned_lock.release()) def test_lock_context_manager_acquire_argument(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() blocking_value = 10.12 lock = self._coord.get_lock(name) with mock.patch.object(lock, 'acquire', wraps=True, autospec=True) as \ @@ -834,7 +847,7 @@ class TestAPI(tests.TestCaseSkipNotImplemented): mock_acquire.assert_called_once_with(blocking_value) def test_lock_context_manager_acquire_argument_timeout(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock1 = self._coord.get_lock(name) lock2 = self._coord.get_lock(name) with lock1: @@ -845,13 +858,13 @@ class TestAPI(tests.TestCaseSkipNotImplemented): pass def test_get_lock_locked_twice(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock = self._coord.get_lock(name) with lock: self.assertFalse(lock.acquire(blocking=False)) def test_get_multiple_locks_with_same_coord(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock1 = self._coord.get_lock(name) lock2 = self._coord.get_lock(name) self.assertTrue(lock1.acquire()) @@ -860,19 +873,19 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self.assertTrue(lock1.release()) def test_ensure_acquire_release_return(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock1 = self._coord.get_lock(name) self.assertTrue(lock1.acquire()) self.assertTrue(lock1.release()) self.assertFalse(lock1.release()) def test_get_lock_multiple_coords(self): - member_id2 = self._get_random_uuid() + member_id2 = tests.get_random_uuid() client2 = tooz.coordination.get_coordinator(self.url, member_id2) client2.start() - lock_name = self._get_random_uuid() + lock_name = tests.get_random_uuid() lock = self._coord.get_lock(lock_name) self.assertTrue(lock.acquire()) @@ -889,16 +902,12 @@ class TestAPI(tests.TestCaseSkipNotImplemented): self._coord.start() def do_test_name_property(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock = self._coord.get_lock(name) self.assertEqual(name, lock.name) - @staticmethod - def _get_random_uuid(): - return str(uuid.uuid4()).encode('ascii') - def test_acquire_twice_no_deadlock_releasing(self): - name = self._get_random_uuid() + name = tests.get_random_uuid() lock = self._coord.get_lock(name) self.assertTrue(lock.acquire(blocking=False)) self.assertFalse(lock.acquire(blocking=False)) diff --git a/tooz/tests/test_etcd.py b/tooz/tests/test_etcd.py new file mode 100644 index 0000000..bbc99e5 --- /dev/null +++ b/tooz/tests/test_etcd.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2016 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock +from testtools import testcase + +import tooz.coordination + + +class TestEtcd(testcase.TestCase): + FAKE_URL = "etcd://mocked-not-really-localhost:2379" + FAKE_MEMBER_ID = "mocked-not-really-member" + + def setUp(self): + super(TestEtcd, self).setUp() + self._coord = tooz.coordination.get_coordinator(self.FAKE_URL, + self.FAKE_MEMBER_ID) + + def test_multiple_locks_etcd_wait_index(self): + lock = self._coord.get_lock('mocked-not-really-random') + + return_values = [ + {'errorCode': {}, 'node': {}, 'index': 10}, + {'errorCode': None, 'node': {}, 'index': 10} + ] + with mock.patch.object(lock.client, 'put', side_effect=return_values): + with mock.patch.object(lock.client, 'get') as mocked_get: + self.assertTrue(lock.acquire()) + mocked_get.assert_called_once() + call = str(mocked_get.call_args) + self.assertIn("waitIndex=11", call) diff --git a/tooz/tests/test_memcache.py b/tooz/tests/test_memcache.py index 2b1b08c..787a96b 100644 --- a/tooz/tests/test_memcache.py +++ b/tooz/tests/test_memcache.py @@ -16,7 +16,6 @@ # under the License. import socket -import uuid try: from unittest import mock @@ -26,6 +25,7 @@ except ImportError: from testtools import testcase from tooz import coordination +from tooz import tests class TestMemcacheDriverFailures(testcase.TestCase): @@ -34,7 +34,7 @@ class TestMemcacheDriverFailures(testcase.TestCase): @mock.patch('pymemcache.client.PooledClient') def test_client_failure_start(self, mock_client_cls): mock_client_cls.side_effect = socket.timeout('timed-out') - member_id = str(uuid.uuid4()).encode('ascii') + member_id = tests.get_random_uuid() coord = coordination.get_coordinator(self.FAKE_URL, member_id) self.assertRaises(coordination.ToozConnectionError, coord.start) @@ -42,29 +42,29 @@ class TestMemcacheDriverFailures(testcase.TestCase): def test_client_failure_join(self, mock_client_cls): mock_client = mock.MagicMock() mock_client_cls.return_value = mock_client - member_id = str(uuid.uuid4()).encode('ascii') + member_id = tests.get_random_uuid() coord = coordination.get_coordinator(self.FAKE_URL, member_id) coord.start() mock_client.gets.side_effect = socket.timeout('timed-out') - fut = coord.join_group(str(uuid.uuid4()).encode('ascii')) + fut = coord.join_group(tests.get_random_uuid()) self.assertRaises(coordination.ToozConnectionError, fut.get) @mock.patch('pymemcache.client.PooledClient') def test_client_failure_leave(self, mock_client_cls): mock_client = mock.MagicMock() mock_client_cls.return_value = mock_client - member_id = str(uuid.uuid4()).encode('ascii') + member_id = tests.get_random_uuid() coord = coordination.get_coordinator(self.FAKE_URL, member_id) coord.start() mock_client.gets.side_effect = socket.timeout('timed-out') - fut = coord.leave_group(str(uuid.uuid4()).encode('ascii')) + fut = coord.leave_group(tests.get_random_uuid()) self.assertRaises(coordination.ToozConnectionError, fut.get) @mock.patch('pymemcache.client.PooledClient') def test_client_failure_heartbeat(self, mock_client_cls): mock_client = mock.MagicMock() mock_client_cls.return_value = mock_client - member_id = str(uuid.uuid4()).encode('ascii') + member_id = tests.get_random_uuid() coord = coordination.get_coordinator(self.FAKE_URL, member_id) coord.start() mock_client.set.side_effect = socket.timeout('timed-out') @@ -77,7 +77,7 @@ class TestMemcacheDriverFailures(testcase.TestCase): mock_run_watchers): mock_client = mock.MagicMock() mock_client_cls.return_value = mock_client - member_id = str(uuid.uuid4()).encode('ascii') + member_id = tests.get_random_uuid() coord = coordination.get_coordinator(self.FAKE_URL, member_id) coord.start() coord.run_watchers() diff --git a/tooz/tests/test_mysql.py b/tooz/tests/test_mysql.py index b8b3b5d..01cc93f 100644 --- a/tooz/tests/test_mysql.py +++ b/tooz/tests/test_mysql.py @@ -15,12 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. -import uuid - from oslo_utils import encodeutils from testtools import testcase from tooz import coordination +from tooz import tests class TestMySQLDriver(testcase.TestCase): @@ -37,7 +36,7 @@ class TestMySQLDriver(testcase.TestCase): raise coord = coordination.get_coordinator(url, - str(uuid.uuid4()).encode('ascii')) + tests.get_random_uuid()) self.addCleanup(_safe_stop, coord) return coord diff --git a/tooz/tests/test_postgresql.py b/tooz/tests/test_postgresql.py index 5dee9fc..24d86ea 100644 --- a/tooz/tests/test_postgresql.py +++ b/tooz/tests/test_postgresql.py @@ -14,8 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -import uuid - try: # Added in python 3.3+ from unittest import mock @@ -27,6 +25,7 @@ import testtools from testtools import testcase from tooz import coordination +from tooz import tests # Handle the case gracefully where the driver is not installed. try: @@ -57,7 +56,7 @@ class TestPostgreSQLFailures(testcase.TestCase): raise coord = coordination.get_coordinator(self.FAKE_URL, - str(uuid.uuid4()).encode('ascii')) + tests.get_random_uuid()) self.addCleanup(_safe_stop, coord) return coord @@ -5,7 +5,7 @@ envlist = py27,py34,py27-zookeeper,py34-zookeeper,py27-redis,py34-redis,py27-sen [testenv] # We need to install a bit more than just `test' because those drivers have # custom tests that we always run -deps = .[test,zake,ipc,memcached,mysql] +deps = .[test,zake,ipc,memcached,mysql,etcd] py{27,34}-zookeeper: .[zookeeper] py{27,34}-redis: .[redis] py{27,34}-sentinel: .[redis] |
