def test_cannot_initialize_backend(self): msg = ( "Cannot initialize `MIGRATION_QUORUM_BACKEND` backend " "'syzygy.quorum.backends.cache.CacheQuorum' with {'unsupported': True}" ) with self.assertRaisesMessage(ImproperlyConfigured, msg): join_quorum("foo", 1)
def test_misconfigured_setting(self): msg = ( "The `MIGRATION_QUORUM_BACKEND` setting must either be an import " "path string or a dict with a 'backend' path key string" ) with self.assertRaisesMessage(ImproperlyConfigured, msg): join_quorum("foo", 1)
def test_missing_setting(self): msg = ( "The `MIGRATION_QUORUM_BACKEND` setting must be configured " "for syzygy.quorum to be used" ) with self.assertRaisesMessage(ImproperlyConfigured, msg): join_quorum("foo", 1)
def test_namespace_reuse(self): namespace = str(uuid.uuid4()) self.assertFalse(join_quorum(namespace, 2)) self.assertTrue(join_quorum(namespace, 2)) self.assertTrue(poll_quorum(namespace, 2)) # Once quorum is reached its associated is immediately cleared and reusable. self.assertFalse(join_quorum(namespace, 2)) self.assertTrue(join_quorum(namespace, 2)) self.assertTrue(poll_quorum(namespace, 2))
def _handle_quorum(self, quorum: int, quorum_timeout: int, options: dict) -> Iterator[bool]: """ Context manager that handles migration application quorum by only allowing a single caller to proceed with application and preventing exit attempts until the application is completes. This ensures only a single invocation is allowed to proceed once quorum is reached and that context can only be exited once the invocation application succeeds. """ if quorum < 2: yield True return verbosity = options["verbosity"] plan = self._get_plan(**options) if not plan: yield True return database = options["database"] plan_hash = hash_plan(plan) pre_namespace = f"pre:{database}:{plan_hash}" post_namespace = f"post:{database}:{plan_hash}" if join_quorum(pre_namespace, quorum): if verbosity: self.stdout.write( "Reached pre-migrate quorum, proceeding with planned migrations..." ) yield True if verbosity: self.stdout.write("Waiting for post-migrate quorum...") duration = self._join_or_poll_until_quorum(post_namespace, quorum, quorum_timeout) if verbosity: self.stdout.write( f"Reached post-migrate quorum after {duration:.2f}s...") return yield False if verbosity: self.stdout.write("Waiting for pre-migrate quorum...") duration = self._poll_until_quorum(pre_namespace, quorum, quorum_timeout) if verbosity: self.stdout.write( f"Reached pre-migrate quorum after {duration:.2f}s...") self.stdout.write( "Waiting for migrations to be applied by remote party...") duration = self._join_or_poll_until_quorum(post_namespace, quorum, quorum_timeout) if verbosity: self.stdout.write( f"Reached post-migrate quorum after {duration:.2f}s...") self.stdout.write("Migrations applied by remote party") return
def _join_or_poll_until_quorum(self, namespace: str, quorum: int, quorum_timeout: int) -> float: if join_quorum(namespace, quorum): return 0 return self._poll_until_quorum(namespace, quorum, quorum_timeout)
def achieve_quorum(namespace): if join_quorum(namespace, quorum): return True while not poll_quorum(namespace, quorum): time.sleep(0.01) return False
def test_multiple(self): namespace = str(uuid.uuid4()) self.assertFalse(join_quorum(namespace, 2)) self.assertFalse(poll_quorum(namespace, 2)) self.assertTrue(join_quorum(namespace, 2)) self.assertTrue(poll_quorum(namespace, 2))
def test_cannot_import_backend(self): msg = "Cannot import `MIGRATION_QUORUM_BACKEND` backend 'syzygy.void'" with self.assertRaisesMessage(ImproperlyConfigured, msg): join_quorum("foo", 1)