mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Fix infinite recursion in in replicatefrom tags (#3072)
Besides that: 1. fix problem with is_physical_slot() methods, it was returning false positives for logical slots. 2. Fix a little issue with replicatefrom docs. Close https://github.com/zalando/patroni/issues/3068
This commit is contained in:
@@ -395,7 +395,7 @@ Tags
|
|||||||
----
|
----
|
||||||
- **clonefrom**: ``true`` or ``false``. If set to ``true`` other nodes might prefer to use this node for bootstrap (take ``pg_basebackup`` from). If there are several nodes with ``clonefrom`` tag set to ``true`` the node to bootstrap from will be chosen randomly. The default value is ``false``.
|
- **clonefrom**: ``true`` or ``false``. If set to ``true`` other nodes might prefer to use this node for bootstrap (take ``pg_basebackup`` from). If there are several nodes with ``clonefrom`` tag set to ``true`` the node to bootstrap from will be chosen randomly. The default value is ``false``.
|
||||||
- **noloadbalance**: ``true`` or ``false``. If set to ``true`` the node will return HTTP Status Code 503 for the ``GET /replica`` REST API health-check and therefore will be excluded from the load-balancing. Defaults to ``false``.
|
- **noloadbalance**: ``true`` or ``false``. If set to ``true`` the node will return HTTP Status Code 503 for the ``GET /replica`` REST API health-check and therefore will be excluded from the load-balancing. Defaults to ``false``.
|
||||||
- **replicatefrom**: The IP address/hostname of another replica. Used to support cascading replication.
|
- **replicatefrom**: The name of another replica to replicate from. Used to support cascading replication.
|
||||||
- **nosync**: ``true`` or ``false``. If set to ``true`` the node will never be selected as a synchronous replica.
|
- **nosync**: ``true`` or ``false``. If set to ``true`` the node will never be selected as a synchronous replica.
|
||||||
- **nofailover**: ``true`` or ``false``, controls whether this node is allowed to participate in the leader race and become a leader. Defaults to ``false``, meaning this node _can_ participate in leader races.
|
- **nofailover**: ``true`` or ``false``, controls whether this node is allowed to participate in the leader race and become a leader. Defaults to ``false``, meaning this node _can_ participate in leader races.
|
||||||
- **failover_priority**: integer, controls the priority that this node should have during failover. Nodes with higher priority will be preferred over lower priority nodes if they received/replayed the same amount of WAL. However, nodes with higher values of receive/replay LSN are preferred regardless of their priority. If the ``failover_priority`` is 0 or negative - such node is not allowed to participate in the leader race and to become a leader (similar to ``nofailover: true``).
|
- **failover_priority**: integer, controls the priority that this node should have during failover. Nodes with higher priority will be preferred over lower priority nodes if they received/replayed the same amount of WAL. However, nodes with higher values of receive/replay LSN are preferred regardless of their priority. If the ``failover_priority`` is 0 or negative - such node is not allowed to participate in the leader race and to become a leader (similar to ``nofailover: true``).
|
||||||
|
|||||||
+20
-7
@@ -10,7 +10,7 @@ from copy import deepcopy
|
|||||||
from random import randint
|
from random import randint
|
||||||
from threading import Event, Lock
|
from threading import Event, Lock
|
||||||
from typing import Any, Callable, Collection, Dict, Iterator, List, \
|
from typing import Any, Callable, Collection, Dict, Iterator, List, \
|
||||||
NamedTuple, Optional, Tuple, Type, TYPE_CHECKING, Union
|
NamedTuple, Optional, Set, Tuple, Type, TYPE_CHECKING, Union
|
||||||
from urllib.parse import urlparse, urlunparse, parse_qsl
|
from urllib.parse import urlparse, urlunparse, parse_qsl
|
||||||
|
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
@@ -913,7 +913,9 @@ class Cluster(NamedTuple('Cluster',
|
|||||||
|
|
||||||
:returns: ``True`` if *value* is a physical replication slot, otherwise ``False``.
|
:returns: ``True`` if *value* is a physical replication slot, otherwise ``False``.
|
||||||
"""
|
"""
|
||||||
return not value or isinstance(value, dict) and value.get('type', 'physical') == 'physical'
|
return not value \
|
||||||
|
or (isinstance(value, dict) and not Cluster.is_logical_slot(value)
|
||||||
|
and value.get('type', 'physical') == 'physical')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_logical_slot(value: Union[Any, Dict[str, Any]]) -> bool:
|
def is_logical_slot(value: Union[Any, Dict[str, Any]]) -> bool:
|
||||||
@@ -1179,6 +1181,10 @@ class Cluster(NamedTuple('Cluster',
|
|||||||
|
|
||||||
if global_config.use_slots:
|
if global_config.use_slots:
|
||||||
name = member.name if isinstance(member, Member) else postgresql.name
|
name = member.name if isinstance(member, Member) else postgresql.name
|
||||||
|
|
||||||
|
if not self.get_slot_name_on_primary(name, member):
|
||||||
|
return False
|
||||||
|
|
||||||
members = [m for m in self.members if m.replicatefrom == name and m.name != self.leader_name]
|
members = [m for m in self.members if m.replicatefrom == name and m.name != self.leader_name]
|
||||||
return any(self.should_enforce_hot_standby_feedback(postgresql, m) for m in members)
|
return any(self.should_enforce_hot_standby_feedback(postgresql, m) for m in members)
|
||||||
return False
|
return False
|
||||||
@@ -1197,11 +1203,18 @@ class Cluster(NamedTuple('Cluster',
|
|||||||
|
|
||||||
:returns: the slot name on the primary that is in use for physical replication on this node.
|
:returns: the slot name on the primary that is in use for physical replication on this node.
|
||||||
"""
|
"""
|
||||||
if tags.nostream:
|
seen_nodes: Set[str] = set()
|
||||||
return None
|
while True:
|
||||||
replicatefrom = self.get_member(tags.replicatefrom, False) if tags.replicatefrom else None
|
seen_nodes.add(name)
|
||||||
return self.get_slot_name_on_primary(replicatefrom.name, replicatefrom) \
|
if tags.nostream:
|
||||||
if isinstance(replicatefrom, Member) else slot_name_from_member_name(name)
|
return None
|
||||||
|
replicatefrom = self.get_member(tags.replicatefrom, False) \
|
||||||
|
if tags.replicatefrom and tags.replicatefrom != name else None
|
||||||
|
if not isinstance(replicatefrom, Member):
|
||||||
|
return slot_name_from_member_name(name)
|
||||||
|
if replicatefrom.name in seen_nodes:
|
||||||
|
return None
|
||||||
|
name, tags = replicatefrom.name, replicatefrom
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timeline(self) -> int:
|
def timeline(self) -> int:
|
||||||
|
|||||||
@@ -186,6 +186,32 @@ class TestSlotsHandler(BaseTestPostgresql):
|
|||||||
cluster.get_slot_name_on_primary(self.p.name, stream_node),
|
cluster.get_slot_name_on_primary(self.p.name, stream_node),
|
||||||
'test_4')
|
'test_4')
|
||||||
|
|
||||||
|
def test_get_slot_name_on_primary(self):
|
||||||
|
node1 = Member(0, 'node1', 28, {
|
||||||
|
'state': 'running', 'conn_url': 'postgres://replicator:[email protected]:5436/postgres',
|
||||||
|
'tags': {'replicatefrom': 'node2'}
|
||||||
|
})
|
||||||
|
node2 = Member(0, 'node2', 28, {
|
||||||
|
'state': 'running', 'conn_url': 'postgres://replicator:[email protected]:5436/postgres',
|
||||||
|
'tags': {'replicatefrom': 'node1'}
|
||||||
|
})
|
||||||
|
cluster = Cluster(True, None, self.leader, Status.empty(), [self.leadermem, node1, node2],
|
||||||
|
None, SyncState.empty(), None, None)
|
||||||
|
self.assertIsNone(cluster.get_slot_name_on_primary('node1', node1))
|
||||||
|
|
||||||
|
def test_should_enforce_hot_standby_feedback(self):
|
||||||
|
node1 = Member(0, 'postgresql0', 28, {
|
||||||
|
'state': 'running', 'conn_url': 'postgres://replicator:[email protected]:5436/postgres',
|
||||||
|
'tags': {'replicatefrom': 'postgresql1'}
|
||||||
|
})
|
||||||
|
node2 = Member(0, 'postgresql1', 28, {
|
||||||
|
'state': 'running', 'conn_url': 'postgres://replicator:[email protected]:5436/postgres',
|
||||||
|
'tags': {'replicatefrom': 'postgresql0'}
|
||||||
|
})
|
||||||
|
cluster = Cluster(True, None, self.leader, Status.empty(), [self.leadermem, node1, node2],
|
||||||
|
None, SyncState.empty(), None, None)
|
||||||
|
self.assertFalse(cluster.should_enforce_hot_standby_feedback(self.p, node1))
|
||||||
|
|
||||||
@patch.object(Postgresql, 'is_primary', Mock(return_value=False))
|
@patch.object(Postgresql, 'is_primary', Mock(return_value=False))
|
||||||
def test__ensure_logical_slots_replica(self):
|
def test__ensure_logical_slots_replica(self):
|
||||||
self.p.set_role('replica')
|
self.p.set_role('replica')
|
||||||
|
|||||||
Reference in New Issue
Block a user