Beispiel #1
0
 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)
Beispiel #2
0
 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)
Beispiel #3
0
 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)
Beispiel #4
0
 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))
Beispiel #5
0
    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
Beispiel #6
0
 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)
Beispiel #7
0
 def achieve_quorum(namespace):
     if join_quorum(namespace, quorum):
         return True
     while not poll_quorum(namespace, quorum):
         time.sleep(0.01)
     return False
Beispiel #8
0
 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))
Beispiel #9
0
 def test_cannot_import_backend(self):
     msg = "Cannot import `MIGRATION_QUORUM_BACKEND` backend 'syzygy.void'"
     with self.assertRaisesMessage(ImproperlyConfigured, msg):
         join_quorum("foo", 1)