Author: stefanegli Date: Thu Sep 24 10:09:22 2015 New Revision: 1705024 URL: http://svn.apache.org/viewvc?rev=1705024&view=rev Log: SLING-5030 part 2: when an instance detects that it is ISOLATED_FROM_TOPOLOGY it resets the leaderElectionId - that has the effect that it will be the least likely to become leader - which means that it is put at the end of the instance ordering. This will ensure that the newly elected leader can stay leader - thus reducing the number of leader changes in case of a temporary and then healed network-partitioning situation. Note that this also implies that when an instance does NOT detect the ISOLATED_FROM_TOPOLOGY that it will NOT step down in the leader ordering.
Modified: sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewServiceImpl.java sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/UndefinedClusterViewException.java sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHandler.java sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingView.java sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatHandler.java sling/trunk/bundles/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatTest.java Modified: sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java?rev=1705024&r1=1705023&r2=1705024&view=diff ============================================================================== --- sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java (original) +++ sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java Thu Sep 24 10:09:22 2015 @@ -59,6 +59,7 @@ import org.apache.sling.discovery.Topolo import org.apache.sling.discovery.TopologyView; import org.apache.sling.discovery.impl.cluster.ClusterViewService; import org.apache.sling.discovery.impl.cluster.UndefinedClusterViewException; +import org.apache.sling.discovery.impl.cluster.UndefinedClusterViewException.Reason; import org.apache.sling.discovery.impl.common.DefaultClusterViewImpl; import org.apache.sling.discovery.impl.common.DefaultInstanceDescriptionImpl; import org.apache.sling.discovery.impl.common.heartbeat.HeartbeatHandler; @@ -686,8 +687,26 @@ public class DiscoveryServiceImpl implem // treat it as being cut off from the entire topology, ie we don't // update the announcements but just return // the previous oldView marked as !current - logger.info("getTopology: undefined cluster view: "+e.getClass().getSimpleName()+": "+e); + logger.info("getTopology: undefined cluster view: "+e.getClass().getSimpleName()+": ["+e.getReason()+"] "+e); oldView.markOld(); + if (e.getReason()==Reason.ISOLATED_FROM_TOPOLOGY) { + if (heartbeatHandler!=null) { + // SLING-5030 part 2: when we detect being isolated we should + // step at the end of the leader-election queue and + // that can be achieved by resetting the leaderElectionId + // (which will in turn take effect on the next round of + // voting, or also double-checked when the local instance votes) + // + //TODO: + // Note that when the local instance doesn't notice + // an 'ISOLATED_FROM_TOPOLOGY' case, then the leaderElectionId + // will not be reset. Which means that it then could potentially + // regain leadership. + if (heartbeatHandler.resetLeaderElectionId()) { + logger.info("getTopology: reset leaderElectionId to force this instance to the end of the instance order (thus incl not to remain leader)"); + } + } + } return oldView; } Modified: sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewServiceImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewServiceImpl.java?rev=1705024&r1=1705023&r2=1705024&view=diff ============================================================================== --- sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewServiceImpl.java (original) +++ sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewServiceImpl.java Thu Sep 24 10:09:22 2015 @@ -31,6 +31,7 @@ import org.apache.sling.api.resource.Res import org.apache.sling.discovery.ClusterView; import org.apache.sling.discovery.InstanceDescription; import org.apache.sling.discovery.impl.Config; +import org.apache.sling.discovery.impl.cluster.UndefinedClusterViewException.Reason; import org.apache.sling.discovery.impl.common.View; import org.apache.sling.discovery.impl.common.ViewHelper; import org.apache.sling.discovery.impl.common.resource.EstablishedClusterView; @@ -94,7 +95,8 @@ public class ClusterViewServiceImpl impl public ClusterView getClusterView() throws UndefinedClusterViewException { if (resourceResolverFactory==null) { logger.warn("getClusterView: no resourceResolverFactory set at the moment."); - throw new UndefinedClusterViewException("no resourceResolverFactory set"); + throw new UndefinedClusterViewException(Reason.REPOSITORY_EXCEPTION, + "no resourceResolverFactory set"); } ResourceResolver resourceResolver = null; try { @@ -104,7 +106,8 @@ public class ClusterViewServiceImpl impl View view = ViewHelper.getEstablishedView(resourceResolver, config); if (view == null) { logger.debug("getClusterView: no view established at the moment. isolated mode"); - throw new UndefinedClusterViewException("no established view at the moment"); + throw new UndefinedClusterViewException(Reason.NO_ESTABLISHED_VIEW, + "no established view at the moment"); } EstablishedClusterView clusterViewImpl = new EstablishedClusterView( @@ -124,12 +127,14 @@ public class ClusterViewServiceImpl impl logger.info("getClusterView: the existing established view does not incude the local instance ("+getSlingId()+") yet! Assuming isolated mode. " + "If this occurs at runtime - other than at startup - it could cause a pseudo-network-partition, see SLING-3432. " + "Consider increasing heartbeatTimeout then!"); - throw new UndefinedClusterViewException("established view does not include local instance - isolated"); + throw new UndefinedClusterViewException(Reason.ISOLATED_FROM_TOPOLOGY, + "established view does not include local instance - isolated"); } } catch (LoginException e) { logger.error( "handleEvent: could not log in administratively: " + e, e); - throw new UndefinedClusterViewException("could not log in administratively: "+e); + throw new UndefinedClusterViewException(Reason.REPOSITORY_EXCEPTION, + "could not log in administratively: "+e); } finally { if (resourceResolver != null) { resourceResolver.close(); Modified: sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/UndefinedClusterViewException.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/UndefinedClusterViewException.java?rev=1705024&r1=1705023&r2=1705024&view=diff ============================================================================== --- sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/UndefinedClusterViewException.java (original) +++ sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/UndefinedClusterViewException.java Thu Sep 24 10:09:22 2015 @@ -29,11 +29,35 @@ package org.apache.sling.discovery.impl. */ public class UndefinedClusterViewException extends Exception { - public UndefinedClusterViewException() { + public static enum Reason { + /** used when the local instance is isolated from the topology + * (which is noticed by an established view that does not include + * the local instance) + */ + ISOLATED_FROM_TOPOLOGY, + + /** used when there is no established view yet + * (happens on a fresh installation) + */ + NO_ESTABLISHED_VIEW, + + /** used when we couldn't reach the repository **/ + REPOSITORY_EXCEPTION + } + + private final Reason reason; + + public UndefinedClusterViewException(Reason reason) { super(); + this.reason = reason; } - public UndefinedClusterViewException(String msg) { + public UndefinedClusterViewException(Reason reason, String msg) { super(msg); + this.reason = reason; + } + + public Reason getReason() { + return reason; } } Modified: sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHandler.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHandler.java?rev=1705024&r1=1705023&r2=1705024&view=diff ============================================================================== --- sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHandler.java (original) +++ sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHandler.java Thu Sep 24 10:09:22 2015 @@ -80,6 +80,11 @@ public class VotingHandler implements Ev /** the sling id of the local instance **/ private String slingId; + /** the HeartbeatHandler sets the leaderElectionid - this is subsequently used + * to ensure the leaderElectionId is correctly set upon voting + */ + private volatile String leaderElectionId; + protected void activate(final ComponentContext context) { slingId = slingSettingsService.getSlingId(); logger = LoggerFactory.getLogger(this.getClass().getCanonicalName() @@ -171,13 +176,13 @@ public class VotingHandler implements Ev // ongoingVotingRes, and I have not voted on // ongoingVotingRes yet. // so I vote no there now - ongoingVotingRes.vote(slingId, false); + ongoingVotingRes.vote(slingId, false, null); it.remove(); } else if (!ongoingVotingRes.matchesLiveView(clusterNodesRes, config)) { logger.warn("analyzeVotings: encountered a voting which does not match mine. Voting no: " + ongoingVotingRes); - ongoingVotingRes.vote(slingId, false); + ongoingVotingRes.vote(slingId, false, null); it.remove(); } else if (ongoingVotingRes.isInitiatedBy(slingId) && ongoingVotingRes.hasNoVotes()) { @@ -214,7 +219,7 @@ public class VotingHandler implements Ev logger.debug("analyzeVotings: only one voting found for which I did not yet vote - and it is not mine. I'll vote yes then: " + votingResource); } - votingResource.vote(slingId, true); + votingResource.vote(slingId, true, leaderElectionId); } // otherwise there is more than one voting going on, all matching my @@ -255,15 +260,15 @@ public class VotingHandler implements Ev logger.debug("analyzeVotings: I apparently have not yet voted. So I shall vote now for the lowest id which is: " + lowestVoting); } - lowestVoting.vote(slingId, true); + lowestVoting.vote(slingId, true, leaderElectionId); } else { // otherwise I've already voted, but not for the lowest. which // is a shame. // I shall change my mind! logger.warn("analyzeVotings: I've already voted - but it so happened that there was a lower voting created after I voted... so I shall change my vote from " + myYesVoteResource + " to " + lowestVoting); - myYesVoteResource.vote(slingId, null); - lowestVoting.vote(slingId, true); + myYesVoteResource.vote(slingId, null, null); + lowestVoting.vote(slingId, true, leaderElectionId); } if (logger.isDebugEnabled()) { logger.debug("analyzeVotings: all done now. I've voted yes for " @@ -430,4 +435,9 @@ public class VotingHandler implements Ev logger.debug("promote: done with promotiong. saving."); resourceResolver.commit(); } + + public void setLeaderElectionId(String leaderElectionId) { + logger.info("setLeaderElectionId: leaderElectionId="+leaderElectionId); + this.leaderElectionId = leaderElectionId; + } } \ No newline at end of file Modified: sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingView.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingView.java?rev=1705024&r1=1705023&r2=1705024&view=diff ============================================================================== --- sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingView.java (original) +++ sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingView.java Thu Sep 24 10:09:22 2015 @@ -286,7 +286,8 @@ public class VotingView extends View { * @param slingId the slingId which is voting * @param vote true for a yes-vote, false for a no-vote */ - public void vote(final String slingId, final Boolean vote) { + public void vote(final String slingId, final Boolean vote, + final String leaderElectionId) { if (logger.isDebugEnabled()) { logger.debug("vote: slingId=" + slingId + ", vote=" + vote); } @@ -314,8 +315,21 @@ public class VotingView extends View { logger.warn("vote: got a RepositoryException: "+e, e); } if (shouldVote) { + logger.info("vote: slingId=" + slingId + " is voting vote=" + vote+" on "+getResource()); memberMap.put("vote", vote); memberMap.put("votedAt", Calendar.getInstance()); + String currentLeaderElectionId = memberMap.get("leaderElectionId", String.class); + if (leaderElectionId!=null && + (currentLeaderElectionId == null || !currentLeaderElectionId.equals(leaderElectionId))) { + // SLING-5030 : to ensure leader-step-down after being + // isolated from the cluster, the leaderElectionId must + // be explicitly set upon voting. + // for 99% of the cases not be necessary, + // for the rejoin-after-isolation case however it is + logger.info("vote: changing leaderElectionId on vote to "+leaderElectionId); + memberMap.put("leaderElectionId", leaderElectionId); + memberMap.put("leaderElectionIdCreatedAt", new Date()); + } } } try { Modified: sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatHandler.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatHandler.java?rev=1705024&r1=1705023&r2=1705024&view=diff ============================================================================== --- sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatHandler.java (original) +++ sling/trunk/bundles/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatHandler.java Thu Sep 24 10:09:22 2015 @@ -126,8 +126,15 @@ public class HeartbeatHandler implements private String nextVotingId = UUID.randomUUID().toString(); /** whether or not to reset the leaderElectionId at next heartbeat time **/ - private boolean resetLeaderElectionId = false; + private volatile boolean resetLeaderElectionId = false; + /** SLING-5030 : upon resetLeaderElectionId() a newLeaderElectionId is calculated + * and passed on to the VotingHandler - but the actual storing under ./clusterInstances + * is only done on heartbeats - this field is used to temporarily store the new + * leaderElectionId that the next heartbeat then stores + */ + private volatile String newLeaderElectionId; + /** lock object for synchronizing the run method **/ private final Object lock = new Object(); @@ -278,6 +285,44 @@ public class HeartbeatHandler implements logger.info("triggerHeartbeat: Could not trigger heartbeat: " + e); } } + + /** + * Hook that will cause a reset of the leaderElectionId + * on next invocation of issueClusterLocalHeartbeat. + * @return true if the leaderElectionId was reset - false if that was not + * necessary as that happened earlier already and it has not propagated + * yet to the ./clusterInstances in the meantime + */ + public boolean resetLeaderElectionId() { + if (resetLeaderElectionId) { + // then we already have a reset pending + // resetting twice doesn't work + return false; + } + resetLeaderElectionId = true; + ResourceResolver resourceResolver = null; + try{ + resourceResolver = getResourceResolver(); + if (resourceResolver!=null) { + newLeaderElectionId = newLeaderElectionId(resourceResolver); + if (votingHandler!=null) { + logger.info("resetLeaderElectionId: set new leaderElectionId with votingHandler to: "+newLeaderElectionId); + votingHandler.setLeaderElectionId(newLeaderElectionId); + } else { + logger.info("resetLeaderElectionId: no votingHandler, new leaderElectionId would be: "+newLeaderElectionId); + } + } else { + logger.warn("resetLeaderElectionId: could not login, new leaderElectionId will be calculated upon next heartbeat only!"); + } + } catch (LoginException e) { + logger.error("resetLeaderElectionid: could not login: "+e, e); + } finally { + if (resourceResolver!=null) { + resourceResolver.close(); + } + } + return true; + } /** * Issue a heartbeat. @@ -409,38 +454,16 @@ public class HeartbeatHandler implements new Object[]{runtimeId, endpointsAsString, slingHomePath}); } if (resetLeaderElectionId || !resourceMap.containsKey("leaderElectionId")) { - int maxLongLength = String.valueOf(Long.MAX_VALUE).length(); - String currentTimeMillisStr = String.format("%0" - + maxLongLength + "d", System.currentTimeMillis()); - - final boolean shouldInvertRepositoryDescriptor = config.shouldInvertRepositoryDescriptor(); - String prefix = (shouldInvertRepositoryDescriptor ? "1" : "0"); - - String leaderElectionRepositoryDescriptor = config.getLeaderElectionRepositoryDescriptor(); - if (leaderElectionRepositoryDescriptor!=null && leaderElectionRepositoryDescriptor.length()!=0) { - // when this property is configured, check the value of the repository descriptor - // and if that value is set, include it in the leader election id - - final Session session = resourceResolver.adaptTo(Session.class); - if ( session != null ) { - String value = session.getRepository() - .getDescriptor(leaderElectionRepositoryDescriptor); - if (value != null) { - if (value.equalsIgnoreCase("true")) { - if (!shouldInvertRepositoryDescriptor) { - prefix = "1"; - } else { - prefix = "0"; - } - } - } - } - } - final String newLeaderElectionId = prefix + "_" - + currentTimeMillisStr + "_" + slingId; + // the new leaderElectionId might have been 'pre set' in the field 'newLeaderElectionId' + // if that's the case, use that one, otherwise calculate a new one now + final String newLeaderElectionId = this.newLeaderElectionId!=null ? this.newLeaderElectionId : newLeaderElectionId(resourceResolver); + this.newLeaderElectionId = null; resourceMap.put("leaderElectionId", newLeaderElectionId); resourceMap.put("leaderElectionIdCreatedAt", new Date()); - logger.debug("issueClusterLocalHeartbeat: set leaderElectionId to "+newLeaderElectionId); + logger.info("issueClusterLocalHeartbeat: set leaderElectionId to "+newLeaderElectionId); + if (votingHandler!=null) { + votingHandler.setLeaderElectionId(newLeaderElectionId); + } resetLeaderElectionId = false; } resourceResolver.commit(); @@ -465,6 +488,42 @@ public class HeartbeatHandler implements } } + /** + * Calculate a new leaderElectionId based on the current config and system time + */ + private String newLeaderElectionId(ResourceResolver resourceResolver) { + int maxLongLength = String.valueOf(Long.MAX_VALUE).length(); + String currentTimeMillisStr = String.format("%0" + + maxLongLength + "d", System.currentTimeMillis()); + + final boolean shouldInvertRepositoryDescriptor = config.shouldInvertRepositoryDescriptor(); + String prefix = (shouldInvertRepositoryDescriptor ? "1" : "0"); + + String leaderElectionRepositoryDescriptor = config.getLeaderElectionRepositoryDescriptor(); + if (leaderElectionRepositoryDescriptor!=null && leaderElectionRepositoryDescriptor.length()!=0) { + // when this property is configured, check the value of the repository descriptor + // and if that value is set, include it in the leader election id + + final Session session = resourceResolver.adaptTo(Session.class); + if ( session != null ) { + String value = session.getRepository() + .getDescriptor(leaderElectionRepositoryDescriptor); + if (value != null) { + if (value.equalsIgnoreCase("true")) { + if (!shouldInvertRepositoryDescriptor) { + prefix = "1"; + } else { + prefix = "0"; + } + } + } + } + } + final String newLeaderElectionId = prefix + "_" + + currentTimeMillisStr + "_" + slingId; + return newLeaderElectionId; + } + /** Check whether the established view matches the reality, ie matches the * heartbeats */ Modified: sling/trunk/bundles/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatTest.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatTest.java?rev=1705024&r1=1705023&r2=1705024&view=diff ============================================================================== --- sling/trunk/bundles/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatTest.java (original) +++ sling/trunk/bundles/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatTest.java Thu Sep 24 10:09:22 2015 @@ -138,12 +138,14 @@ public class HeartbeatTest { assertEquals(1, slowMachine.getDiscoveryService().getTopology().getInstances().size()); assertEquals(slowMachine.getSlingId(), slowMachine.getDiscoveryService().getTopology().getInstances().iterator().next().getSlingId()); instances.add(slowMachine); + Thread.sleep(10); // wait 10ms to ensure 'slowMachine' has the lowerst leaderElectionId (to become leader) SimpleTopologyEventListener slowListener = new SimpleTopologyEventListener("slow"); slowMachine.bindTopologyEventListener(slowListener); Instance fastMachine1 = Instance.newClusterInstance("/var/discovery/impl/", "fast1", slowMachine, false, 5, 1, 0); assertEquals(1, fastMachine1.getDiscoveryService().getTopology().getInstances().size()); assertEquals(fastMachine1.getSlingId(), fastMachine1.getDiscoveryService().getTopology().getInstances().iterator().next().getSlingId()); instances.add(fastMachine1); + Thread.sleep(10); // wait 10ms to ensure 'fastMachine1' has the 2nd lowerst leaderElectionId (to become leader during partitioning) SimpleTopologyEventListener fastListener1 = new SimpleTopologyEventListener("fast1"); fastMachine1.bindTopologyEventListener(fastListener1); Instance fastMachine2 = Instance.newClusterInstance("/var/discovery/impl/", "fast2", slowMachine, false, 5, 1, 0); @@ -186,18 +188,23 @@ public class HeartbeatTest { assertNotNull(fastListener1.getLastEvent()); assertEquals(TopologyEvent.Type.TOPOLOGY_INIT, fastListener1.getLastEvent().getType()); assertEquals(5, fastListener1.getLastEvent().getNewView().getInstances().size()); + assertFalse(fastListener1.getLastEvent().getNewView().getLocalInstance().isLeader()); assertNotNull(fastListener2.getLastEvent()); assertEquals(TopologyEvent.Type.TOPOLOGY_INIT, fastListener2.getLastEvent().getType()); assertEquals(5, fastListener2.getLastEvent().getNewView().getInstances().size()); + assertFalse(fastListener2.getLastEvent().getNewView().getLocalInstance().isLeader()); assertNotNull(fastListener3.getLastEvent()); assertEquals(TopologyEvent.Type.TOPOLOGY_INIT, fastListener3.getLastEvent().getType()); assertEquals(5, fastListener3.getLastEvent().getNewView().getInstances().size()); + assertFalse(fastListener3.getLastEvent().getNewView().getLocalInstance().isLeader()); assertNotNull(fastListener4.getLastEvent()); assertEquals(TopologyEvent.Type.TOPOLOGY_INIT, fastListener4.getLastEvent().getType()); assertEquals(5, fastListener4.getLastEvent().getNewView().getInstances().size()); + assertFalse(fastListener4.getLastEvent().getNewView().getLocalInstance().isLeader()); assertNotNull(slowListener.getLastEvent()); assertEquals(TopologyEvent.Type.TOPOLOGY_INIT, slowListener.getLastEvent().getType()); assertEquals(5, slowListener.getLastEvent().getNewView().getInstances().size()); + assertTrue(slowListener.getLastEvent().getNewView().getLocalInstance().isLeader()); // after 7sec the slow instance' heartbeat should have timed out for(int i=0; i<7; i++) { @@ -225,6 +232,11 @@ public class HeartbeatTest { assertEquals(4, fastListener3.getLastEvent().getNewView().getInstances().size()); assertEquals(TopologyEvent.Type.TOPOLOGY_CHANGED, fastListener4.getLastEvent().getType()); assertEquals(4, fastListener4.getLastEvent().getNewView().getInstances().size()); + + assertTrue(fastListener1.getLastEvent().getNewView().getLocalInstance().isLeader()); + assertFalse(fastListener2.getLastEvent().getNewView().getLocalInstance().isLeader()); + assertFalse(fastListener3.getLastEvent().getNewView().getLocalInstance().isLeader()); + assertFalse(fastListener4.getLastEvent().getNewView().getLocalInstance().isLeader()); // and the slow instance should be isolated assertFalse(slowMachine.getDiscoveryService().getTopology().isCurrent()); @@ -276,6 +288,14 @@ public class HeartbeatTest { assertEquals(5, fastListener4.getLastEvent().getNewView().getInstances().size()); assertEquals(TopologyEvent.Type.TOPOLOGY_CHANGED, slowListener.getLastEvent().getType()); assertEquals(5, slowListener.getLastEvent().getNewView().getInstances().size()); + + // SLING-5030 part 2 : after rejoin-after-partitioning the slowMachine1 should again be leader + slowMachine.dumpRepo(); + assertFalse(slowListener.getLastEvent().getNewView().getLocalInstance().isLeader()); + assertTrue(fastListener1.getLastEvent().getNewView().getLocalInstance().isLeader()); + assertFalse(fastListener2.getLastEvent().getNewView().getLocalInstance().isLeader()); + assertFalse(fastListener3.getLastEvent().getNewView().getLocalInstance().isLeader()); + assertFalse(fastListener4.getLastEvent().getNewView().getLocalInstance().isLeader()); } /**