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; }
/** @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(); }