예제 #1
0
    def processNominate(self, nom: Nomination, sender: str):
        """
        Process a Nomination message.

        :param nom: the nomination message
        :param sender: sender address of the nomination
        """
        logger.debug("{}'s elector started processing nominate msg: {}".
                     format(self.name, nom))
        instId = nom.instId
        replica = self.replicas[instId]
        sndrRep = replica.generateName(sender, nom.instId)

        if not self.didReplicaNominate(instId):
            if instId not in self.nominations:
                self.setDefaults(instId)
            self.nominations[instId][replica.name] = nom.name
            self.sendNomination(nom.name, nom.instId, nom.viewNo)
            logger.debug("{} nominating {} for instance {}".
                         format(replica, nom.name, nom.instId),
                         extra={"cli": "PLAIN"})
        else:
            logger.debug("{} already nominated".format(replica.name))
        # Nodes should not be able to vote more than once
        if sndrRep not in self.nominations[instId]:
            self.nominations[instId][sndrRep] = nom.name
            logger.debug("{} attempting to decide primary based on nomination "
                         "request: {} from {}".format(replica, nom, sndrRep))
            self._schedule(partial(self.decidePrimary, instId))
        else:
            self.discard(nom,
                         "already got nomination from {}".
                         format(replica, sndrRep),
                         logger.warning)
예제 #2
0
    def processReelection(self, reelection: Reelection, sender: str):
        """
        Process reelection requests sent by other nodes.
        If quorum is achieved, proceed with the reelection process.

        :param reelection: the reelection request
        :param sender: name of the  node from which the reelection was sent
        """
        logger.debug(
            "{}'s elector started processing reelection msg".format(self.name))
        # Check for election round number to discard any previous
        # reelection round message
        instId = reelection.instId
        replica = self.replicas[instId]
        sndrRep = replica.generateName(sender, reelection.instId)

        if instId not in self.reElectionProposals:
            self.setDefaults(instId)

        expectedRoundDiff = 0 if (replica.name in
                                  self.reElectionProposals[instId]) else 1
        expectedRound = self.reElectionRounds[instId] + expectedRoundDiff

        if not reelection.round == expectedRound:
            self.discard(reelection,
                         "reelection request from {} with round "
                         "number {} does not match expected {}".
                         format(sndrRep, reelection.round, expectedRound),
                         logger.debug)
            return

        if sndrRep not in self.reElectionProposals[instId]:
            self.reElectionProposals[instId][sndrRep] = reelection.tieAmong

            # Check if got reelection messages from at least 2f + 1 nodes (1
            # more than max faulty nodes). Necessary because some nodes may
            # turn out to be malicious and send re-election frequently

            if self.hasReelectionQuorum(instId):
                logger.debug("{} achieved reelection quorum".format(replica),
                             extra={"cli": True})
                # Need to find the most frequent tie reported to avoid `tie`s
                # from malicious nodes. Since lists are not hashable so
                # converting each tie(a list of node names) to a tuple.
                ties = [tuple(t) for t in
                        self.reElectionProposals[instId].values()]
                tieAmong = mostCommonElement(ties)

                self.setElectionDefaults(instId)

                # There was a tie among this and some other node(s), so do a
                # random wait
                if replica.name in tieAmong:
                    # Try to nominate self after a random delay but dont block
                    # until that delay and because a nominate from another
                    # node might be sent
                    self._schedule(partial(self.nominateReplica, instId),
                                   random.randint(1, 3))
                else:
                    # Now try to nominate self again as there is a reelection
                    self.nominateReplica(instId)
            else:
                logger.debug(
                    "{} does not have re-election quorum yet. Got only {}".format(
                        replica, len(self.reElectionProposals[instId])))
        else:
            self.discard(reelection,
                         "already got re-election proposal from {}".
                         format(replica, sndrRep),
                         logger.warning)
예제 #3
0
    def processPrimary(self, prim: Primary, sender: str) -> None:
        """
        Process a vote from a replica to select a particular replica as primary.
        Once 2f + 1 primary declarations have been received, decide on a primary replica.

        :param prim: a vote
        :param sender: the name of the node from which this message was sent
        """
        logger.debug("{}'s elector started processing primary msg from {} : {}"
                     .format(self.name, sender, prim))
        instId = prim.instId
        replica = self.replicas[instId]
        sndrRep = replica.generateName(sender, prim.instId)

        # Nodes should not be able to declare `Primary` winner more than more
        if instId not in self.primaryDeclarations:
            self.setDefaults(instId)
        if sndrRep not in self.primaryDeclarations[instId]:
            self.primaryDeclarations[instId][sndrRep] = prim.name

            # If got more than 2f+1 primary declarations then in a position to
            # decide whether it is the primary or not `2f + 1` declarations
            # are enough because even when all the `f` malicious nodes declare
            # a primary, we still have f+1 primary declarations from
            # non-malicious nodes. One more assumption is that all the non
            # malicious nodes vote for the the same primary

            # Find for which node there are maximum primary declarations.
            # Cant be a tie among 2 nodes since all the non malicious nodes
            # which would be greater than or equal to f+1 would vote for the
            # same node

            if replica.isPrimary is not None:
                logger.debug(
                    "{} Primary already selected; ignoring PRIMARY msg".format(
                        replica))
                return

            if self.hasPrimaryQuorum(instId):
                if replica.isPrimary is None:
                    primary = mostCommonElement(
                        self.primaryDeclarations[instId].values())
                    logger.info("{} selected primary {} for instance {} "
                                "(view {})".
                                format(replica, primary, instId, self.viewNo),
                                extra={"cli": "ANNOUNCE"})
                    logger.debug("{} selected primary on the basis of {}".
                                 format(replica,
                                        self.primaryDeclarations[instId]),
                                 extra={"cli": False})

                    # If the maximum primary declarations are for this node
                    # then make it primary
                    replica.primaryName = primary

                    # If this replica has nominated itself and since the
                    # election is over, reset the flag
                    if self.replicaNominatedForItself == instId:
                        self.replicaNominatedForItself = None

                    self.scheduleElection()
                else:
                    self.discard(prim,
                                 "it already decided primary which is {}".
                                 format(replica.primaryName),
                                 logger.debug)
            else:
                logger.debug(
                    "{} received {} but does it not have primary quorum yet"
                        .format(self.name, prim))
        else:
            self.discard(prim,
                         "already got primary declaration from {}".
                         format(replica, sndrRep),
                         logger.warning)