private boolean updateCurrent(LeaderElection req) {
    boolean isNew = false;

    if (current == null) {
      current = new ElectionState();
      isNew = true;
    } else {
      if (req.getExpires() > current.maxDuration) {
        isNew = false; // If this election was started after the current election, dont do anything!
        logger.info("Ignoring this election - older election already in progress! Not voting!");
        return isNew;
      } else {
        isNew = true;
      }
    }

    if (req.getCandidateId() == this.nodeId) {
      // logger.info("Updating election details in RAFT Election");
      isNew = false;
    }

    current.electionID = req.getElectId();
    current.candidate = req.getCandidateId();
    current.desc = req.getDesc();
    current.maxDuration = req.getExpires();
    current.startedOn = System.currentTimeMillis();
    current.state = req.getAction();
    current.id = -1; // TODO me or sender?
    current.active = true;

    return isNew;
  }
Esempio n. 2
0
  /** @param args */
  public void processRequest(Management mgmt) {
    if (!mgmt.hasElection()) return;

    LeaderElection req = mgmt.getElection();

    // when a new node joins the network it will want to know who the leader
    // is - we kind of ram this request-response in the process request
    // though there has to be a better place for it
    if (req.getAction().getNumber() == LeaderElection.ElectAction.WHOISTHELEADER_VALUE) {
      respondToWhoIsTheLeader(mgmt);
      return;
    } else if (req.getAction().getNumber() == LeaderElection.ElectAction.THELEADERIS_VALUE) {
      logger.info(
          "Node "
              + conf.getNodeId()
              + " got an answer on who the leader is. Its Node "
              + req.getCandidateId());
      this.leaderNode = req.getCandidateId();
      this.termId = req.getTermId();
      // the current term id
      // TODO Last log index to be synced
      return;
    }

    // else fall through to an election
    // if the time has expired, an election is initiated.
    if (req.hasExpires()) {
      long ct = System.currentTimeMillis();
      if (ct > req.getExpires()) {
        // ran out of time so the election is over
        if (election != null) election.clear();
        return;
      }
    }

    Management rtn = electionInstance().process(mgmt);
    if (rtn != null) ConnectionManager.broadcast(rtn);
  }
  /*
   * (non-Javadoc)
   *
   * @see poke.server.election.Election#process(eye.Comm.LeaderElection)
   *
   * @return The Management instance returned represents the message to send
   * on. If null, no action is required. Not a great choice to convey to the
   * caller; can be improved upon easily.
   */
  @Override
  public Management process(Management mgmt) {
    if (!mgmt.hasElection()) return null;

    LeaderElection req = mgmt.getElection();
    if (req.getExpires() <= System.currentTimeMillis()) {
      // election has expired without a conclusion?
    }

    Management rtn = null;

    if (req.getAction().getNumber() == ElectAction.DECLAREELECTION_VALUE) {
      // an election is declared!

      // required to eliminate duplicate messages - on a declaration,
      // should not happen if the network does not have cycles
      List<VectorClock> rtes = mgmt.getHeader().getPathList();
      for (VectorClock rp : rtes) {
        if (rp.getNodeId() == this.nodeId) {
          // message has already been sent to me, don't use and
          // forward
          return null;
        }
      }

      // I got here because the election is unknown to me

      // this 'if debug is on' should cover the below dozen or so
      // println()s. It is here to help with seeing when an election
      // occurs.
      if (logger.isDebugEnabled()) {}

      System.out.println("\n\n*********************************************************");
      System.out.println("   RAFT ELECTION: Election declared");
      System.out.println("   Election ID:  " + req.getElectId());
      System.out.println("   Rcv from:     Node " + mgmt.getHeader().getOriginator());
      System.out.println("   Expires:      " + new Date(req.getExpires()));
      System.out.println("   Nominates:    Node " + req.getCandidateId());
      System.out.println("   Desc:         " + req.getDesc());
      System.out.print("   Routing tbl:  [");
      for (VectorClock rp : rtes)
        System.out.print(
            "Node " + rp.getNodeId() + " (" + rp.getVersion() + "," + rp.getTime() + "), ");
      System.out.println("]");
      System.out.println("*********************************************************\n\n");

      // sync master IDs to current election
      ElectionIDGenerator.setMasterID(req.getElectId());

      /**
       * a new election can be declared over an existing election.
       *
       * <p>TODO need to have an monotonically increasing ID that we can test
       */
      boolean isNew = updateCurrent(req);
      rtn = castVote(mgmt, isNew);

    } else if (req.getAction().getNumber() == ElectAction.DECLAREVOID_VALUE) {
      // no one was elected, I am dropping into standby mode
      logger.info("TODO: no one was elected, I am dropping into standby mode");
      this.clear();
      notify(false, null);
    } else if (req.getAction().getNumber() == ElectAction.DECLAREWINNER_VALUE) {
      // some node declared itself the leader
      logger.info(
          "Election "
              + req.getElectId()
              + ": Node "
              + req.getCandidateId()
              + " is declared the leader");
      updateCurrent(mgmt.getElection());
      current.active = false; // it's over
      notify(true, req.getCandidateId());
    } else if (req.getAction().getNumber() == ElectAction.ABSTAIN_VALUE) {
      // for some reason, a node declines to vote - therefore, do nothing
    } else if (req.getAction().getNumber() == ElectAction.NOMINATE_VALUE) {
      boolean isNew = updateCurrent(mgmt.getElection());
      rtn = castVote(mgmt, isNew);
    } else {
      // this is me!
    }

    return rtn;
  }
  /**
   * cast a vote based on what I know (my ID) and where the message has traveled.
   *
   * <p>This is not a pretty piece of code, nor is the problem as we cannot ensure consistent
   * behavior.
   *
   * @param mgmt
   * @param isNew
   * @return
   */
  private synchronized Management castVote(Management mgmt, boolean isNew) {
    ElectionState st = new ElectionState();

    if (!mgmt.hasElection()) return null;

    if (current == null || !current.isActive()) {
      return null;
    }

    LeaderElection req = mgmt.getElection();
    if (req.getExpires() <= System.currentTimeMillis()) {
      logger.info("Node " + this.nodeId + " says election expired - not voting");
      return null;
    }

    logger.info("casting vote in election " + req.getElectId());

    // DANGER! If we return because this node ID is in the list, we have a
    // high chance an election will not converge as the maxHops determines
    // if the graph has been traversed!
    boolean allowCycles = true;

    if (!allowCycles) {
      List<VectorClock> rtes = mgmt.getHeader().getPathList();
      for (VectorClock rp : rtes) {
        if (rp.getNodeId() == this.nodeId) {
          // logger.info("Node " + this.nodeId +
          // " already in the routing path - not voting");
          return null;
        }
      }
    }

    // okay, the message is new (to me) so I want to determine if I should
    // nominate myself

    LeaderElection.Builder elb = LeaderElection.newBuilder();
    MgmtHeader.Builder mhb = MgmtHeader.newBuilder();
    mhb.setTime(System.currentTimeMillis());
    mhb.setSecurityCode(-999); // TODO add security

    // reversing path. If I'm the farthest a message can travel, reverse the
    // sending
    if (elb.getHops() == 0) mhb.clearPath();
    else mhb.addAllPath(mgmt.getHeader().getPathList());

    mhb.setOriginator(mgmt.getHeader().getOriginator());

    elb.setElectId(req.getElectId());
    elb.setAction(ElectAction.NOMINATE);
    elb.setDesc(req.getDesc());
    elb.setExpires(req.getExpires());
    elb.setCandidateId(req.getCandidateId());

    // my vote
    if (req.getCandidateId() == this.nodeId) {
      // if I am not in the list and the candidate is myself, I can
      // declare myself to be the leader.
      //
      // this is non-deterministic as it assumes the message has
      // reached all nodes in the network (because we know the
      // diameter or the number of nodes).
      //
      // can end up with a partitioned graph of leaders if hops <
      // diameter!

      // this notify goes out to on-node listeners and will arrive before
      // the other nodes receive notice.
      // notify(true, this.nodeId);

      current.incrementVoteCount();
      // System.out.println("Candidate:"+current.getVoteCount());
      if (current.getVoteCount() > (ConnectionManager.getNumMgmtConnections()) / 2) {
        elb.setAction(ElectAction.DECLAREWINNER);
        elb.setHops(mgmt.getHeader().getPathCount());
      } else {
        System.out.println("No majority");
        // ElectionManager.getInstance().leaderNode=null;
        return null;
      }
      logger.info("Node " + this.nodeId + " is declaring itself the leader");
      ElectionManager.getInstance().leaderNode = this.nodeId;

    } else {
      current.incrementVoteCount();
      // System.out.println("Follower"+current.getVoteCount());

      if (req.getHops() == -1) elb.setHops(-1);
      else elb.setHops(req.getHops() - 1);

      if (elb.getHops() == 0) {
        // reverse travel of the message to ensure it gets back to
        // the originator
        elb.setHops(mgmt.getHeader().getPathCount());

        // no clear winner, send back the candidate with the highest
        // known ID. So, if a candidate sees itself, it will
        // declare itself to be the winner (see above).
      } else {
        // forwarding the message on so, keep the history where the
        // message has been
        mhb.addAllPath(mgmt.getHeader().getPathList());
      }
    }
    // add myself (may allow duplicate entries, if cycling is allowed)
    VectorClock.Builder rpb = VectorClock.newBuilder();
    rpb.setNodeId(this.nodeId);
    rpb.setTime(System.currentTimeMillis());
    rpb.setVersion(req.getElectId());
    mhb.addPath(rpb);

    Management.Builder mb = Management.newBuilder();
    mb.setHeader(mhb.build());
    mb.setElection(elb.build());

    return mb.build();
  }