mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
To enable quorum commit: ```diff $ patronictl.py edit-config --- +++ @@ -5,3 +5,4 @@ use_pg_rewind: true retry_timeout: 10 ttl: 30 +synchronous_mode: quorum Apply these changes? [y/N]: y Configuration changed ``` By default Patroni will use `ANY 1(list,of,stanbys)` in `synchronous_standby_names`. That is, only one node out of listed replicas will be used for quorum. If you want to increase the number of quorum nodes it is possible to do it with: ```diff $ patronictl edit-config --- +++ @@ -6,3 +6,4 @@ retry_timeout: 10 synchronous_mode: quorum ttl: 30 +synchronous_node_count: 2 Apply these changes? [y/N]: y Configuration changed ``` Good old `synchronous_mode: on` is still supported. Close https://github.com/patroni/patroni/issues/664 Close https://github.com/zalando/patroni/pull/672
474 lines
25 KiB
Python
474 lines
25 KiB
Python
import unittest
|
|
|
|
from typing import List, Set, Tuple
|
|
|
|
from patroni.quorum import QuorumStateResolver, QuorumError
|
|
|
|
|
|
class QuorumTest(unittest.TestCase):
|
|
|
|
def check_state_transitions(self, leader: str, quorum: int, voters: Set[str], numsync: int, sync: Set[str],
|
|
numsync_confirmed: int, active: Set[str], sync_wanted: int, leader_wanted: str,
|
|
expected: List[Tuple[str, str, int, Set[str]]]) -> None:
|
|
kwargs = {
|
|
'leader': leader, 'quorum': quorum, 'voters': voters,
|
|
'numsync': numsync, 'sync': sync, 'numsync_confirmed': numsync_confirmed,
|
|
'active': active, 'sync_wanted': sync_wanted, 'leader_wanted': leader_wanted
|
|
}
|
|
result = list(QuorumStateResolver(**kwargs))
|
|
self.assertEqual(result, expected)
|
|
|
|
# also check interrupted transitions
|
|
if len(result) > 0 and result[0][0] != 'restart' and kwargs['leader'] == result[0][1]:
|
|
if result[0][0] == 'sync':
|
|
kwargs.update(numsync=result[0][2], sync=result[0][3])
|
|
else:
|
|
kwargs.update(leader=result[0][1], quorum=result[0][2], voters=result[0][3])
|
|
kwargs['expected'] = expected[1:]
|
|
self.check_state_transitions(**kwargs)
|
|
|
|
def test_1111(self):
|
|
leader = 'a'
|
|
|
|
# Add node
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set(),
|
|
numsync=0, sync=set(), numsync_confirmed=0, active=set('b'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('sync', leader, 1, set('b')),
|
|
('restart', leader, 0, set()),
|
|
])
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set(),
|
|
numsync=1, sync=set('b'), numsync_confirmed=1, active=set('b'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 0, set('b'))
|
|
])
|
|
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set(),
|
|
numsync=0, sync=set(), numsync_confirmed=0, active=set('bcde'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('sync', leader, 2, set('bcde')),
|
|
('restart', leader, 0, set()),
|
|
])
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set(),
|
|
numsync=2, sync=set('bcde'), numsync_confirmed=1, active=set('bcde'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 3, set('bcde')),
|
|
])
|
|
|
|
def test_1222(self):
|
|
"""2 node cluster"""
|
|
leader = 'a'
|
|
|
|
# Active set matches state
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('b'),
|
|
numsync=1, sync=set('b'), numsync_confirmed=1, active=set('b'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[])
|
|
|
|
# Add node by increasing quorum
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('b'),
|
|
numsync=1, sync=set('b'), numsync_confirmed=1, active=set('BC'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('quorum', leader, 1, set('bC')),
|
|
('sync', leader, 1, set('bC')),
|
|
])
|
|
|
|
# Add node by increasing sync
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('b'),
|
|
numsync=1, sync=set('b'), numsync_confirmed=1, active=set('bc'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('sync', leader, 2, set('bc')),
|
|
('quorum', leader, 1, set('bc')),
|
|
])
|
|
# Reduce quorum after added node caught up
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bc'),
|
|
numsync=2, sync=set('bc'), numsync_confirmed=2, active=set('bc'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 0, set('bc')),
|
|
])
|
|
|
|
# Add multiple nodes by increasing both sync and quorum
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('b'),
|
|
numsync=1, sync=set('b'), numsync_confirmed=1, active=set('BCdE'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('sync', leader, 2, set('bC')),
|
|
('quorum', leader, 3, set('bCdE')),
|
|
('sync', leader, 2, set('bCdE')),
|
|
])
|
|
# Reduce quorum after added nodes caught up
|
|
self.check_state_transitions(leader=leader, quorum=3, voters=set('bcde'),
|
|
numsync=2, sync=set('bcde'), numsync_confirmed=3, active=set('bcde'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 2, set('bcde')),
|
|
])
|
|
|
|
# Primary is alone
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('b'),
|
|
numsync=1, sync=set('b'), numsync_confirmed=0, active=set(),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('quorum', leader, 0, set()),
|
|
('sync', leader, 0, set()),
|
|
])
|
|
|
|
# Swap out sync replica
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('b'),
|
|
numsync=1, sync=set('b'), numsync_confirmed=0, active=set('c'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('quorum', leader, 0, set()),
|
|
('sync', leader, 1, set('c')),
|
|
('restart', leader, 0, set()),
|
|
])
|
|
# Update quorum when added node caught up
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set(),
|
|
numsync=1, sync=set('c'), numsync_confirmed=1, active=set('c'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('quorum', leader, 0, set('c')),
|
|
])
|
|
|
|
def test_1233(self):
|
|
"""Interrupted transition from 2 node cluster to 3 node fully sync cluster"""
|
|
leader = 'a'
|
|
|
|
# Node c went away, transition back to 2 node cluster
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('b'),
|
|
numsync=2, sync=set('bc'), numsync_confirmed=1, active=set('b'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('sync', leader, 1, set('b')),
|
|
])
|
|
|
|
# Node c is available transition to larger quorum set, but not yet caught up.
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('b'),
|
|
numsync=2, sync=set('bc'), numsync_confirmed=1, active=set('bc'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 1, set('bc')),
|
|
])
|
|
|
|
# Add in a new node at the same time, but node c didn't caught up yet
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('b'),
|
|
numsync=2, sync=set('bc'), numsync_confirmed=1, active=set('bcd'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 2, set('bcd')),
|
|
('sync', leader, 2, set('bcd')),
|
|
])
|
|
# All sync nodes caught up, reduce quorum
|
|
self.check_state_transitions(leader=leader, quorum=2, voters=set('bcd'),
|
|
numsync=2, sync=set('bcd'), numsync_confirmed=3, active=set('bcd'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 1, set('bcd')),
|
|
])
|
|
|
|
# Change replication factor at the same time
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('b'),
|
|
numsync=2, sync=set('bc'), numsync_confirmed=1, active=set('bc'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('quorum', leader, 1, set('bc')),
|
|
('sync', leader, 1, set('bc')),
|
|
])
|
|
|
|
def test_2322(self):
|
|
"""Interrupted transition from 2 node cluster to 3 node cluster with replication factor 2"""
|
|
leader = 'a'
|
|
|
|
# Node c went away, transition back to 2 node cluster
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bc'),
|
|
numsync=1, sync=set('b'), numsync_confirmed=1, active=set('b'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('quorum', leader, 0, set('b')),
|
|
])
|
|
|
|
# Node c is available transition to larger quorum set.
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bc'),
|
|
numsync=1, sync=set('b'), numsync_confirmed=1, active=set('bc'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('sync', leader, 1, set('bc')),
|
|
])
|
|
|
|
# Add in a new node at the same time
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bc'),
|
|
numsync=1, sync=set('b'), numsync_confirmed=1, active=set('bcd'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('sync', leader, 1, set('bc')),
|
|
('quorum', leader, 2, set('bcd')),
|
|
('sync', leader, 1, set('bcd')),
|
|
])
|
|
|
|
# Convert to a fully synced cluster
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bc'),
|
|
numsync=1, sync=set('b'), numsync_confirmed=1, active=set('bc'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('sync', leader, 2, set('bc')),
|
|
])
|
|
# Reduce quorum after all nodes caught up
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bc'),
|
|
numsync=2, sync=set('bc'), numsync_confirmed=2, active=set('bc'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 0, set('bc')),
|
|
])
|
|
|
|
def test_3535(self):
|
|
leader = 'a'
|
|
|
|
# remove nodes
|
|
self.check_state_transitions(leader=leader, quorum=2, voters=set('bcde'),
|
|
numsync=2, sync=set('bcde'), numsync_confirmed=2, active=set('bc'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('sync', leader, 2, set('bc')),
|
|
('quorum', leader, 0, set('bc')),
|
|
])
|
|
self.check_state_transitions(leader=leader, quorum=2, voters=set('bcde'),
|
|
numsync=2, sync=set('bcde'), numsync_confirmed=3, active=set('bcd'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('sync', leader, 2, set('bcd')),
|
|
('quorum', leader, 1, set('bcd')),
|
|
])
|
|
|
|
# remove nodes and decrease sync
|
|
self.check_state_transitions(leader=leader, quorum=2, voters=set('bcde'),
|
|
numsync=2, sync=set('bcde'), numsync_confirmed=2, active=set('bc'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('sync', leader, 2, set('bc')),
|
|
('quorum', leader, 1, set('bc')),
|
|
('sync', leader, 1, set('bc')),
|
|
])
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bcde'),
|
|
numsync=3, sync=set('bcde'), numsync_confirmed=2, active=set('bc'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('sync', leader, 3, set('bcd')),
|
|
('quorum', leader, 1, set('bc')),
|
|
('sync', leader, 1, set('bc')),
|
|
])
|
|
|
|
# Increase replication factor and decrease quorum
|
|
self.check_state_transitions(leader=leader, quorum=2, voters=set('bcde'),
|
|
numsync=2, sync=set('bcde'), numsync_confirmed=2, active=set('bcde'),
|
|
sync_wanted=3, leader_wanted=leader, expected=[
|
|
('sync', leader, 3, set('bcde')),
|
|
])
|
|
# decrease quorum after more nodes caught up
|
|
self.check_state_transitions(leader=leader, quorum=2, voters=set('bcde'),
|
|
numsync=3, sync=set('bcde'), numsync_confirmed=3, active=set('bcde'),
|
|
sync_wanted=3, leader_wanted=leader, expected=[
|
|
('quorum', leader, 1, set('bcde')),
|
|
])
|
|
|
|
# Add node with decreasing sync and increasing quorum
|
|
self.check_state_transitions(leader=leader, quorum=2, voters=set('bcde'),
|
|
numsync=2, sync=set('bcde'), numsync_confirmed=2, active=set('bcdef'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
# increase quorum by 2, 1 for added node and another for reduced sync
|
|
('quorum', leader, 4, set('bcdef')),
|
|
# now reduce replication factor to requested value
|
|
('sync', leader, 1, set('bcdef')),
|
|
])
|
|
|
|
# Remove node with increasing sync and decreasing quorum
|
|
self.check_state_transitions(leader=leader, quorum=2, voters=set('bcde'),
|
|
numsync=2, sync=set('bcde'), numsync_confirmed=2, active=set('bcd'),
|
|
sync_wanted=3, leader_wanted=leader, expected=[
|
|
# node e removed from sync wth replication factor increase
|
|
('sync', leader, 3, set('bcd')),
|
|
# node e removed from voters with quorum decrease
|
|
('quorum', leader, 1, set('bcd')),
|
|
])
|
|
|
|
def test_remove_nosync_node(self):
|
|
leader = 'a'
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('bc'),
|
|
numsync=2, sync=set('bc'), numsync_confirmed=1, active=set('b'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 0, set('b')),
|
|
('sync', leader, 1, set('b'))
|
|
])
|
|
|
|
def test_swap_sync_node(self):
|
|
leader = 'a'
|
|
self.check_state_transitions(leader=leader, quorum=0, voters=set('bc'),
|
|
numsync=2, sync=set('bc'), numsync_confirmed=1, active=set('bd'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 0, set('b')),
|
|
('sync', leader, 2, set('bd')),
|
|
('quorum', leader, 1, set('bd'))
|
|
])
|
|
|
|
def test_promotion(self):
|
|
# Beginning stat: 'a' in the primary, 1 of bcd in sync
|
|
# a fails, c gets quorum votes and promotes
|
|
self.check_state_transitions(leader='a', quorum=2, voters=set('bcd'),
|
|
numsync=0, sync=set(), numsync_confirmed=0, active=set(),
|
|
sync_wanted=1, leader_wanted='c', expected=[
|
|
('sync', 'a', 1, set('abd')), # set a and b to sync
|
|
('quorum', 'c', 2, set('abd')), # set c as a leader and move a to voters
|
|
# and stop because there are no active nodes
|
|
])
|
|
|
|
# next loop, b managed to reconnect
|
|
self.check_state_transitions(leader='c', quorum=2, voters=set('abd'),
|
|
numsync=1, sync=set('abd'), numsync_confirmed=0, active=set('b'),
|
|
sync_wanted=1, leader_wanted='c', expected=[
|
|
('sync', 'c', 1, set('b')), # remove a from sync as inactive
|
|
('quorum', 'c', 0, set('b')), # remove a from voters and reduce quorum
|
|
])
|
|
|
|
# alternative reality: next loop, no one reconnected
|
|
self.check_state_transitions(leader='c', quorum=2, voters=set('abd'),
|
|
numsync=1, sync=set('abd'), numsync_confirmed=0, active=set(),
|
|
sync_wanted=1, leader_wanted='c', expected=[
|
|
('quorum', 'c', 0, set()),
|
|
('sync', 'c', 0, set()),
|
|
])
|
|
|
|
def test_nonsync_promotion(self):
|
|
# Beginning state: 1 of bc in sync. e.g. (a primary, ssn = ANY 1 (b c))
|
|
# a fails, d sees b and c, knows that it is in sync and decides to promote.
|
|
# We include in sync state former primary increasing replication factor
|
|
# and let situation resolve. Node d ssn=ANY 1 (b c)
|
|
leader = 'd'
|
|
self.check_state_transitions(leader='a', quorum=1, voters=set('bc'),
|
|
numsync=0, sync=set(), numsync_confirmed=0, active=set(),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
# Set a, b, and c to sync and increase replication factor
|
|
('sync', 'a', 2, set('abc')),
|
|
# Set ourselves as the leader and move the old leader to voters
|
|
('quorum', leader, 1, set('abc')),
|
|
# and stop because there are no active nodes
|
|
])
|
|
# next loop, b and c managed to reconnect
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('abc'),
|
|
numsync=2, sync=set('abc'), numsync_confirmed=0, active=set('bc'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('sync', leader, 2, set('bc')), # Remove a from being synced to.
|
|
('quorum', leader, 1, set('bc')), # Remove a from quorum
|
|
('sync', leader, 1, set('bc')), # Can now reduce replication factor back
|
|
])
|
|
# alternative reality: next loop, no one reconnected
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('abc'),
|
|
numsync=2, sync=set('abc'), numsync_confirmed=0, active=set(),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
('quorum', leader, 0, set()),
|
|
('sync', leader, 0, set()),
|
|
])
|
|
|
|
def test_invalid_states(self):
|
|
leader = 'a'
|
|
|
|
# Main invariant is not satisfied, system is in an unsafe state
|
|
resolver = QuorumStateResolver(leader=leader, quorum=0, voters=set('bc'),
|
|
numsync=1, sync=set('bc'), numsync_confirmed=1,
|
|
active=set('bc'), sync_wanted=1, leader_wanted=leader)
|
|
self.assertRaises(QuorumError, resolver.check_invariants)
|
|
self.assertEqual(list(resolver), [
|
|
('quorum', leader, 1, set('bc'))
|
|
])
|
|
|
|
# Quorum and sync states mismatched, somebody other than Patroni modified system state
|
|
resolver = QuorumStateResolver(leader=leader, quorum=1, voters=set('bc'),
|
|
numsync=2, sync=set('bd'), numsync_confirmed=1,
|
|
active=set('bd'), sync_wanted=1, leader_wanted=leader)
|
|
self.assertRaises(QuorumError, resolver.check_invariants)
|
|
self.assertEqual(list(resolver), [
|
|
('quorum', leader, 1, set('bd')),
|
|
('sync', leader, 1, set('bd')),
|
|
])
|
|
self.assertTrue(repr(resolver.sync).startswith('<CaseInsensitiveSet'))
|
|
|
|
def test_sync_high_quorum_low_safety_margin_high(self):
|
|
leader = 'a'
|
|
|
|
self.check_state_transitions(leader=leader, quorum=2, voters=set('bcdef'),
|
|
numsync=4, sync=set('bcdef'), numsync_confirmed=3, active=set('bcdef'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
('quorum', leader, 3, set('bcdef')), # Adjust quorum requirements
|
|
('sync', leader, 2, set('bcdef')), # Reduce synchronization
|
|
])
|
|
|
|
def test_quorum_update(self):
|
|
resolver = QuorumStateResolver(leader='a', quorum=1, voters=set('bc'), numsync=1, sync=set('bc'),
|
|
numsync_confirmed=1, active=set('bc'), sync_wanted=1, leader_wanted='a')
|
|
self.assertRaises(QuorumError, list, resolver.quorum_update(-1, set()))
|
|
self.assertRaises(QuorumError, list, resolver.quorum_update(1, set()))
|
|
|
|
def test_sync_update(self):
|
|
resolver = QuorumStateResolver(leader='a', quorum=1, voters=set('bc'), numsync=1, sync=set('bc'),
|
|
numsync_confirmed=1, active=set('bc'), sync_wanted=1, leader_wanted='a')
|
|
self.assertRaises(QuorumError, list, resolver.sync_update(-1, set()))
|
|
self.assertRaises(QuorumError, list, resolver.sync_update(1, set()))
|
|
|
|
def test_remove_nodes_with_decreasing_sync(self):
|
|
leader = 'a'
|
|
|
|
# Remove node with decreasing sync
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bcdef'),
|
|
numsync=4, sync=set('bcdef'), numsync_confirmed=2, active=set('bcd'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
# node f removed from sync
|
|
('sync', leader, 4, set('bcde')),
|
|
# nodes e and f removed from voters with quorum decrease
|
|
('quorum', leader, 1, set('bcd')),
|
|
# node e removed from sync with replication factor decrease
|
|
('sync', leader, 2, set('bcd')),
|
|
])
|
|
|
|
# Interrupted state, and node g joined
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bcdef'),
|
|
numsync=4, sync=set('bcde'), numsync_confirmed=2, active=set('bcdg'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
# remove nodes e and f from voters
|
|
('quorum', leader, 1, set('bcd')),
|
|
# remove node e from sync and reduce replication factor
|
|
('sync', leader, 3, set('bcd')),
|
|
# add node g to voters with quorum increase
|
|
('quorum', leader, 2, set('bcdg')),
|
|
# add node g to sync and reduce replication factor
|
|
('sync', leader, 2, set('bcdg')),
|
|
])
|
|
|
|
# node f returned
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bcdef'),
|
|
numsync=4, sync=set('bcde'), numsync_confirmed=2, active=set('bcdf'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
# replace node e with f in sync
|
|
('sync', leader, 4, set('bcdf')),
|
|
# remove nodes e from voters with quorum decrease
|
|
('quorum', leader, 2, set('bcdf')),
|
|
# reduce replication factor as it was requested
|
|
('sync', leader, 2, set('bcdf')),
|
|
])
|
|
|
|
# node e returned
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bcdef'),
|
|
numsync=4, sync=set('bcde'), numsync_confirmed=2, active=set('bcde'),
|
|
sync_wanted=2, leader_wanted=leader, expected=[
|
|
# remove nodes f from voters with quorum decrease
|
|
('quorum', leader, 2, set('bcde')),
|
|
# reduce replication factor as it was requested
|
|
('sync', leader, 2, set('bcde')),
|
|
])
|
|
|
|
# node b is also lost
|
|
self.check_state_transitions(leader=leader, quorum=1, voters=set('bcdef'),
|
|
numsync=4, sync=set('bcde'), numsync_confirmed=2, active=set('cd'),
|
|
sync_wanted=1, leader_wanted=leader, expected=[
|
|
# remove nodes b, e, and f from voters
|
|
('quorum', leader, 1, set('cd')),
|
|
# remove nodes b and e from sync with replication factor decrease
|
|
('sync', leader, 1, set('cd')),
|
|
])
|
|
|
|
def test_empty_ssn(self):
|
|
# Beginning stat: 'a' in the primary, 1 of bc in sync
|
|
# a fails, c gets quorum votes and promotes
|
|
self.check_state_transitions(leader='a', quorum=1, voters=set('bc'),
|
|
numsync=1, sync=set(), numsync_confirmed=0, active=set(),
|
|
sync_wanted=1, leader_wanted='c', expected=[
|
|
('sync', 'a', 1, set('ab')), # remove a from sync as inactive
|
|
('quorum', 'c', 1, set('ab')), # set c as a leader and move a to voters
|
|
# and stop because there are no active nodes
|
|
])
|
|
|
|
# next loop, b managed to reconnect
|
|
self.check_state_transitions(leader='c', quorum=1, voters=set('ab'),
|
|
numsync=1, sync=set('ab'), numsync_confirmed=0, active=set('b'),
|
|
sync_wanted=1, leader_wanted='c', expected=[
|
|
('sync', 'c', 1, set('b')), # remove a from sync as inactive
|
|
('quorum', 'c', 0, set('b')), # remove a from voters and reduce quorum
|
|
])
|