/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.bigdata.quorum; import com.bigdata.ha.HAGlue; import com.bigdata.ha.QuorumService; /** * Wraps the token/join transitions in a testable manner. * * Both enables JUnit testing and also provides context to represent * better abstractions on the quorum/service states. */ public class QuorumTokenTransitions { final long currentQuorumToken; final long newQuorumToken; final long currentHAReadyToken; public final boolean didBreak; public final boolean didMeet; public final boolean didJoinMetQuorum; public final boolean didLeaveMetQuorum; final private boolean wasMet; final private boolean isMet; final private boolean isJoined; final private boolean wasJoined; @Override public final String toString() { final StringBuilder sb = new StringBuilder(); sb.append(getClass()); sb.append("{oldQuorumToken=" + currentQuorumToken); sb.append(",newQuorumToken=" + newQuorumToken); sb.append(",oldHAReadyToken=" + currentHAReadyToken); sb.append(",didBreak=" + didBreak); sb.append(",didMeet=" + didMeet); sb.append(",didJoinMetQuorum=" + didJoinMetQuorum); sb.append(",didLeaveMetQuorum=" + didLeaveMetQuorum); sb.append("}"); return sb.toString(); } public QuorumTokenTransitions(final long currentQuorumToken, final long newQuorumToken, final QuorumService<HAGlue> service, final long haReady) { this(currentQuorumToken, newQuorumToken, service != null && service.isJoinedMember(newQuorumToken), haReady); } public QuorumTokenTransitions(final long currentQuorumToken, final long newQuorumToken, final boolean joined, final long haReady) { this.currentHAReadyToken = haReady; this.currentQuorumToken = currentQuorumToken; this.newQuorumToken = newQuorumToken; isJoined = joined; wasJoined = haReady != Quorum.NO_QUORUM; wasMet = currentQuorumToken != Quorum.NO_QUORUM; isMet = newQuorumToken != Quorum.NO_QUORUM; // Both quorum token and haReadyToken agree with newValue. final boolean noTokenChange = currentQuorumToken == newQuorumToken && currentQuorumToken == currentHAReadyToken; /* * TODO: more understanding required as to the effect of this clause */ if (noTokenChange && isJoined) { didBreak = false; didMeet = false; didJoinMetQuorum = didJoin(); didLeaveMetQuorum = false; } else if (isBreak()) { didBreak = true; // quorum break. didMeet = false; didJoinMetQuorum = false; didLeaveMetQuorum = wasJoined; // if service was joined with met quorum, then it just left the met quorum. } else if (isMeet()) { /* * Quorum meet. * * We must wait for the lock to update the token. */ didBreak = false; didMeet = true; // quorum meet. didJoinMetQuorum = false; didLeaveMetQuorum = false; } else if (didJoin()) { /* * This service is joining a quorum that is already met. */ didBreak = false; didMeet = false; didJoinMetQuorum = true; // service joined with met quorum. didLeaveMetQuorum = false; } else if (didLeaveMet()) { /* * This service is leaving a quorum that is already met (but * this is not a quorum break since the new token is not * NO_QUORUM). */ didBreak = false; didMeet = false; didJoinMetQuorum = false; didLeaveMetQuorum = true; // service left met quorum. quorum // still met. } else { didBreak = false; didMeet = false; didJoinMetQuorum = false; didLeaveMetQuorum = false; // throw new AssertionError("Bad state from: oldToken=" + currentQuorumToken + ", newToken=" + newQuorumToken + ", haReady=" + haReady + ", isJoined=" + isJoined); } checkStates(); } // TODO Document rationale for each assertion. private void checkStates() { if (wasJoined && wasMet && currentHAReadyToken > currentQuorumToken) { throw new AssertionError("haReady greater than current token"); } if (wasMet && isMet && newQuorumToken < currentQuorumToken) { throw new AssertionError("next token less than current token"); } if (wasMet && isMet && newQuorumToken != currentQuorumToken) { /* * This service observed a quorum token change without observing the * quorum break. The service CAN NOT go directly into the new * quorum. It MUST first do a serviceLeave(). */ throw new AssertionError( "New quorum token without quorum break first, current: " + currentQuorumToken + ", new: " + newQuorumToken); } if (didMeet && didJoinMetQuorum) { /* * It is not possible to both join with an existing quorum and to be * one of the services that caused a quorum to meet. These * conditions are exclusive. */ throw new AssertionError("didMeet && didJoinMetQuorum"); } /** * This is a bit odd, it is okay, but probably didLeaveMetQuorum will * only be true iff isJoined */ // if (didBreak && didLeaveMetQuorum) { // throw new AssertionError("didBreak && didLeaveMetQuorum"); // } // if (didLeaveMetQuorum && !isJoined) { // TODO Why not valid? // throw new AssertionError("didLeaveMetQuorum && !isJoined"); // } } /* * Methods for making decisions in the ctor. They are NOT for public * use. There are simple public fields that report out the decisions * make using these methods. */ // public final boolean noChange() { // return currentQuorumToken == newQuorumToken; // } private final boolean isBreak() { return wasMet && !isMet; } private final boolean isMeet() { return isMet & !wasMet; } private final boolean didJoin() { return isMet && isJoined && !wasJoined; } // private final boolean didLeave() { // return isBreak() && wasJoined; // } private final boolean didLeaveMet() { return isMet && wasJoined && !isJoined; } }