class ExternalNode (practical.Node):
'''
This implementation is completely passive. An external entity must monitor peer nodes
for failure and call prepare() when the node should attempt to acquire leadership
of the Paxos instance. Continual polling of the prepare() function and prevention
of eternal leadership battles is also the responsibility of the caller.
When leadership is acquired, the node will broadcast a leadership proclamation,
declaring itself the leader of the instance. This relieves peer nodes of the
responsibility of tracking promises for all prepare messages.
'''
def __init__(self, messenger, my_uid, quorum_size, leader_uid=None):
super(ExternalNode, self).__init__(messenger, my_uid, quorum_size)
self.leader_uid = leader_uid
self.leader_proposal_id = ProposalID(1, leader_uid)
self._nacks = set()
if self.node_uid == leader_uid:
self.leader = True
self.proposal_id = ProposalID(self.next_proposal_number, self.node_uid)
self.next_proposal_number += 1
def prepare(self, *args, **kwargs):
self._nacks.clear()
return super(ExternalNode, self).prepare(*args, **kwargs)
def recv_leadership_proclamation(self, from_uid, proposal_id):
if proposal_id > self.leader_proposal_id:
old_leader_uid = self.leader_uid
self.leader_uid = from_uid
self.leader_proposal_id = proposal_id
self.observe_proposal( from_uid, proposal_id )
if old_leader_uid == self.node_uid:
self.messenger.on_leadership_lost()
self.messenger.on_leadership_change( old_leader_uid, from_uid )
def recv_promise(self, acceptor_uid, proposal_id, prev_proposal_id, prev_proposal_value):
pre_leader = self.leader
super(ExternalNode, self).recv_promise(acceptor_uid, proposal_id, prev_proposal_id, prev_proposal_value)
if not pre_leader and self.leader:
old_leader_uid = self.leader_uid
self.leader_uid = self.node_uid
self.leader_proposal_id = self.proposal_id
self.messenger.send_leadership_proclamation( proposal_id )
self.messenger.on_leadership_change( old_leader_uid, self.node_uid )
def recv_accept_nack(self, from_uid, proposal_id, promised_id):
if proposal_id == self.proposal_id:
self._nacks.add(from_uid)
if self.leader and len(self._nacks) >= self.quorum_size:
self.leader = False
self.promises_rcvd = set()
self.leader_uid = None
self.leader_proposal_id = None
self.messenger.on_leadership_lost()
self.messenger.on_leadership_change(self.node_uid, None)
self.observe_proposal( from_uid, promised_id )