Fix bug in the Cluster class (#2794)

The `workers` attribute when not passed explicitly was set to the same mutable `dict` object every time.

Problem was introduced in #2652
This commit is contained in:
Alexander Kukushkin
2023-08-01 13:57:46 +02:00
committed by GitHub
parent e4703d4f74
commit ec61aede85
+39 -25
View File
@@ -555,36 +555,50 @@ class TimelineHistory(NamedTuple):
return TimelineHistory(version, value, lines)
class Cluster(NamedTuple):
"""Immutable object (namedtuple) which represents PostgreSQL cluster.
class Cluster(NamedTuple('Cluster',
[('initialize', Optional[str]),
('config', Optional[ClusterConfig]),
('leader', Optional[Leader]),
('last_lsn', int),
('members', List[Member]),
('failover', Optional[Failover]),
('sync', SyncState),
('history', Optional[TimelineHistory]),
('slots', Optional[Dict[str, int]]),
('failsafe', Optional[Dict[str, str]]),
('workers', Dict[int, 'Cluster'])])):
"""Immutable object (namedtuple) which represents PostgreSQL or Citus cluster.
.. note::
We are using an old-style attribute declaration here because otherwise it is not possible to override `__new__`
method. Without it the *workers* by default gets always the same :class:`dict` object that could be mutated.
Consists of the following fields:
:param initialize: shows whether this cluster has initialization key stored in DC or not.
:param config: global dynamic configuration, reference to `ClusterConfig` object
:param leader: `Leader` object which represents current leader of the cluster
:param last_lsn: int or long object containing position of last known leader LSN.
This value is stored in the `/status` key or `/optime/leader` (legacy) key
:param members: list of Member object, all PostgreSQL cluster members including leader
:param failover: reference to `Failover` object
:param sync: reference to `SyncState` object, last observed synchronous replication state.
:param history: reference to `TimelineHistory` object
:param slots: state of permanent logical replication slots on the primary in the format: {"slot_name": int}
:param failsafe: failsafe topology. Node is allowed to become the leader only if its name is found in this list.
:param workers: workers of the Citus cluster, optional. Format: {int(group): Cluster()}
:ivar initialize: shows whether this cluster has initialization key stored in DC or not.
:ivar config: global dynamic configuration, reference to `ClusterConfig` object.
:ivar leader: :class:`Leader` object which represents current leader of the cluster.
:ivar last_lsn: :class:int object containing position of last known leader LSN.
This value is stored in the `/status` key or `/optime/leader` (legacy) key.
:ivar members: list of:class:` Member` objects, all PostgreSQL cluster members including leader
:ivar failover: reference to :class:`Failover` object.
:ivar sync: reference to :class:`SyncState` object, last observed synchronous replication state.
:ivar history: reference to `TimelineHistory` object.
:ivar slots: state of permanent logical replication slots on the primary in the format: {"slot_name": int}.
:ivar failsafe: failsafe topology. Node is allowed to become the leader only if its name is found in this list.
:ivar workers: dictionary of workers of the Citus cluster, optional. Each key is an :class:`int` representing
the group, and the corresponding value is a :class:`Cluster` instance.
"""
initialize: Optional[str]
config: Optional[ClusterConfig]
leader: Optional[Leader]
last_lsn: int
members: List[Member]
failover: Optional[Failover]
sync: SyncState
history: Optional[TimelineHistory]
slots: Optional[Dict[str, int]]
failsafe: Optional[Dict[str, str]]
workers: Dict[int, 'Cluster'] = {}
def __new__(cls, *args: Any, **kwargs: Any):
"""Make workers argument optional and set it to an empty dict object."""
if len(args) < len(cls._fields) and 'workers' not in kwargs:
kwargs['workers'] = {}
return super(Cluster, cls).__new__(cls, *args, **kwargs)
@staticmethod
def empty() -> 'Cluster':
"""Produce an empty :class:`Cluster` instance."""
return Cluster(None, None, None, 0, [], None, SyncState.empty(), None, None, None)
def is_empty(self):