blob: 0e80f7f5f1f82d84fa5760d6a68718189fd4b254 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.discovery.oak;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.jackrabbit.api.JackrabbitRepository;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
import org.apache.jackrabbit.oak.spi.state.NodeStore;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.discovery.InstanceDescription;
import org.apache.sling.discovery.base.commons.OakViewCheckerFactory;
import org.apache.sling.discovery.base.commons.UndefinedClusterViewException;
import org.apache.sling.discovery.base.its.setup.mock.MockFactory;
import org.apache.sling.discovery.commons.providers.BaseTopologyView;
import org.apache.sling.discovery.commons.providers.DefaultClusterView;
import org.apache.sling.discovery.commons.providers.DummyTopologyView;
import org.apache.sling.discovery.commons.providers.spi.LocalClusterView;
import org.apache.sling.discovery.commons.providers.spi.base.DescriptorHelper;
import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptor;
import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptorBuilder;
import org.apache.sling.discovery.commons.providers.spi.base.IdMapService;
import org.apache.sling.discovery.commons.providers.spi.base.IdMapServiceAccessor;
import org.apache.sling.discovery.commons.providers.spi.base.RepositoryTestHelper;
import org.apache.sling.discovery.commons.providers.spi.base.SyncTokenService;
import org.apache.sling.discovery.oak.cluster.ClusterReader;
import org.apache.sling.discovery.oak.cluster.InstanceReadResult;
import org.apache.sling.discovery.oak.cluster.OakClusterViewService;
import org.apache.sling.discovery.oak.pinger.OakViewChecker;
import org.apache.sling.settings.SlingSettingsService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
public class PartialStartupTest {
private MemoryNodeStore nodeStore;
private List<MiniInstance> instances = new LinkedList<>();
static class Identifiable {
final int me;
Identifiable(int me) {
this.me = me;
}
}
static class MiniInstance extends Identifiable {
private final Config config;
private final String slingId;
private final JackrabbitRepository jcrRepo;
private final ResourceResolverFactory resourceResolverFactory;
private final IdMapService idMapService;
private final SlingSettingsService settingsService;
private final OakClusterViewService ocvService;
MiniInstance(int me, NodeStore nodeStore, final boolean suppressPartiallyStartedInstances, final boolean syncTokenEnabled) {
this(me, UUID.randomUUID().toString(), nodeStore, suppressPartiallyStartedInstances, syncTokenEnabled);
}
MiniInstance(int me, String slingId, NodeStore nodeStore, final boolean suppressPartiallyStartedInstances, final boolean syncTokenEnabled) {
this(me, slingId, nodeStore, new Config() {
@Override
public long getClusterSyncServiceIntervalMillis() {
return 9999;
}
@Override
public boolean getSuppressPartiallyStartedInstances() {
return suppressPartiallyStartedInstances;
}
@Override
public boolean getSyncTokenEnabled() {
return syncTokenEnabled;
}
});
}
MiniInstance(int me, String slingId, NodeStore nodeStore, Config config) {
super(me);
this.slingId = slingId;
jcrRepo = (JackrabbitRepository) RepositoryTestHelper.createOakRepository(nodeStore);
this.config = config;
settingsService = Mockito.mock(SlingSettingsService.class);
Mockito.when(settingsService.getSlingId()).thenReturn(slingId);
resourceResolverFactory = MockFactory.mockResourceResolverFactory(jcrRepo);
idMapService = IdMapService.testConstructor(config, settingsService, resourceResolverFactory);
ocvService = OakClusterViewService.testConstructor(settingsService, resourceResolverFactory, idMapService, config);
}
private void setDescriptor(DiscoveryLiteDescriptorBuilder builder) throws Exception {
DescriptorHelper.setDescriptor(resourceResolverFactory, DiscoveryLiteDescriptor.OAK_DISCOVERYLITE_CLUSTERVIEW, builder == null ? null : builder.me(me).asJson());
}
private void initLeaderElectionId() {
OakViewChecker viewChecker = OakViewCheckerFactory.createOakViewChecker(settingsService, resourceResolverFactory, config);
viewChecker.resetLeaderElectionId();
}
private void storeSyncToken(String syncToken) {
SyncTokenService syncTokenService = SyncTokenService.testConstructorAndActivate(config, resourceResolverFactory, settingsService);
final String clusterId = UUID.randomUUID().toString();
final DefaultClusterView cluster = new DefaultClusterView(clusterId);
BaseTopologyView view = new DummyTopologyView(syncToken).addInstance(slingId, cluster, true, true);
final AtomicBoolean done = new AtomicBoolean();
syncTokenService.sync(view, new Runnable() {
@Override
public void run() {
done.set(true);
}
});
assertTrue(done.get());
}
private void storeSlingId() throws Exception {
storeSlingId(simpleDesc(1).me(me));
}
private void storeSlingId(DiscoveryLiteDescriptorBuilder descBuilder) throws Exception {
setDescriptor(descBuilder);
try {
assertTrue(idMapService.waitForInit(5000));
} finally {
setDescriptor(null);
}
}
private LocalClusterView getLocalClusterView(DiscoveryLiteDescriptorBuilder descBuilder) throws Exception {
setDescriptor(descBuilder);
Log4jErrorCatcher catcher = Log4jErrorCatcher.installErrorCatcher(ocvService.getClass());
try {
return ocvService.getLocalClusterView();
} finally {
catcher.uninstall();
setDescriptor(null);
String theLastErrorMsg = catcher.getLastErrorMsg();
if (theLastErrorMsg != null) {
fail("error encountered : " + theLastErrorMsg);
}
}
}
public void tearDown() {
IdMapServiceAccessor.deactivate(idMapService);
}
@Override
public String toString() {
return "a MiniInstance[clusterNodeId = " + me + ", slingId = " + slingId + "]";
}
}
private MiniInstance withNewSlingId(Identifiable baseInstance) throws Exception {
return withSlingId(baseInstance, UUID.randomUUID().toString());
}
private MiniInstance withSlingId(Identifiable baseInstance, String newSlingId) throws Exception {
return new MiniInstance(baseInstance.me, newSlingId, nodeStore, true, true);
}
private MiniInstance create(int id, boolean storeSlingId, boolean storeLeaderElectionId, String syncIdOrNull) throws Exception {
return create(true, true, id, storeSlingId, storeLeaderElectionId, syncIdOrNull);
}
private MiniInstance create(boolean suppressPartiallyStartedInstances, boolean syncTokenEnabled, int id, boolean storeSlingId, boolean storeLeaderElectionId, String syncIdOrNull) throws Exception {
MiniInstance instance = new MiniInstance(id, nodeStore, suppressPartiallyStartedInstances, syncTokenEnabled);
if (storeSlingId) {
instance.storeSlingId();
}
if (storeLeaderElectionId) {
instance.initLeaderElectionId();
}
if (storeSlingId && storeLeaderElectionId) {
try {
@SuppressWarnings("deprecation")
ClusterReader r = new ClusterReader(instance.resourceResolverFactory.getAdministrativeResourceResolver(null), instance.config, instance.idMapService, null);
InstanceReadResult i = r.readInstance(id, false, -1);
assertNotNull(i.getInstanceInfo());
System.out.println(" instance " + id + " read " + i.getInstanceInfo());
} catch (LoginException | PersistenceException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
if (syncIdOrNull != null) {
instance.storeSyncToken(syncIdOrNull);
}
instances.add(instance);
return instance;
}
@Before
public void setup() throws Exception {
nodeStore = new MemoryNodeStore();
}
@After
public void tearDown() throws Exception {
for (MiniInstance instance : instances) {
instance.tearDown();
}
}
private static DiscoveryLiteDescriptorBuilder desc() {
return new DiscoveryLiteDescriptorBuilder();
}
private static DiscoveryLiteDescriptorBuilder simpleDesc(int seqNum) {
return new DiscoveryLiteDescriptorBuilder().id("anyId").setFinal(true).seq(seqNum);
}
private static DiscoveryLiteDescriptorBuilder simpleDesc(int seqNum, Identifiable...instances) {
final DiscoveryLiteDescriptorBuilder builder = new DiscoveryLiteDescriptorBuilder().id("anyId").setFinal(true).seq(seqNum);
Integer[] activeIds = new Integer[instances.length];
for (int i = 0; i < instances.length; i++ ) {
activeIds[i] = instances[i].me;
}
builder.activeIds(activeIds);
return builder;
}
private static void assertNoPartiallyStarted(LocalClusterView view) {
assertFalse(view.hasPartiallyStartedInstances());
}
private static void assertPartiallyStarted(LocalClusterView view, int... suppressedIds) {
if (suppressedIds.length == 0) {
assertFalse(view.hasPartiallyStartedInstances());
return;
}
assertTrue(view.hasPartiallyStartedInstances());
final Set<Integer> suppressed = new HashSet<>();
for (int i : suppressedIds) {
suppressed.add(i);
}
assertEquals(suppressed, view.getPartiallyStartedClusterNodeIds());
}
@Test
public void testNoDescriptor() throws Exception {
doTestNoDescriptor(create(false, false, 1, false, false, null));
doTestNoDescriptor(create(true, false, 1, false, false, null));
doTestNoDescriptor(create(false, true, 1, false, false, null));
doTestNoDescriptor(create(true, true, 1, false, false, null));
}
private void doTestNoDescriptor(MiniInstance instance) throws Exception {
try {
instance.getLocalClusterView(null);
fail("should have thrown exception");
} catch(UndefinedClusterViewException ucve) {
// this empty catch is okay
}
}
@Test
public void testNotFinal() throws Exception {
doTestNotFinal(create(false, false, 1, false, false, null));
doTestNotFinal(create(true, false, 1, false, false, null));
doTestNotFinal(create(false, true, 1, false, false, null));
doTestNotFinal(create(true, true, 1, false, false, null));
}
private void doTestNotFinal(MiniInstance instance) throws Exception {
try {
instance.getLocalClusterView(desc().id("id").me(1).activeIds(1));
fail("should have thrown exception");
} catch(UndefinedClusterViewException ucve) {
// this empty catch is okay
}
}
@Test
public void testNoLeaderElectionId() throws Exception {
MiniInstance instance = create(1, true, false, null);
try {
instance.getLocalClusterView(simpleDesc(1).me(1).activeIds(1));
fail("should have thrown exception");
} catch(UndefinedClusterViewException ucve) {
// this empty catch is okay
}
}
@Test
public void testSimple() throws Exception {
doTestSimple(create(false, false, 1, true, true, null));
doTestSimple(create(true, false, 1, true, true, null));
doTestSimple(create(false, true, 1, true, true, null));
doTestSimple(create(true, true, 1, true, true, null));
}
private void doTestSimple(MiniInstance instance) throws Exception {
LocalClusterView localView = instance.getLocalClusterView(simpleDesc(1).me(instance.me).activeIds(instance.me));
assertNotNull(localView);
assertNoPartiallyStarted(localView);
assertEquals(1, localView.getInstances().size());
assertNotNull(localView.getLocalInstance());
assertEquals(instance.slingId, localView.getLocalInstance().getSlingId());
}
@Test
public void testNoLocalSyncTokenYet() throws Exception {
MiniInstance instance1 = create(1, true, true, null);
MiniInstance instance2 = create(2, true, true, "2");
MiniInstance instance3 = create(3, false, false, null);
try {
instance1.getLocalClusterView(simpleDesc(2, instance1, instance2, instance3));
fail("should complain");
} catch(Exception e) {
// this empty catch is okay
}
instance3 = create(3, true, false, null);
try {
instance1.getLocalClusterView(simpleDesc(2, instance1, instance2, instance3));
fail("should complain");
} catch(Exception e) {
// this empty catch is okay
}
instance3 = create(3, false, true, null);
try {
instance1.getLocalClusterView(simpleDesc(2, instance1, instance2, instance3));
fail("should complain");
} catch(Exception e) {
// this empty catch is okay
}
instance3 = create(3, false, false, "2");
try {
instance1.getLocalClusterView(simpleDesc(2, instance1, instance2, instance3));
fail("should complain");
} catch(Exception e) {
// this empty catch is okay
}
instance3 = create(3, true, false, "2");
try {
instance1.getLocalClusterView(simpleDesc(2, instance1, instance2, instance3));
fail("should complain");
} catch(Exception e) {
// this empty catch is okay
}
instance3 = create(3, false, true, "2");
try {
instance1.getLocalClusterView(simpleDesc(2, instance1, instance2, instance3));
fail("should complain");
} catch(Exception e) {
// this empty catch is okay
}
System.out.println(" => instance1 is " + instance1);
System.out.println(" => instance2 is " + instance2);
instance3 = create(3, true, true, null);
instance1.getLocalClusterView(simpleDesc(2, instance1, instance2, instance3));
instance3 = create(3, true, true, "2");
instance1.getLocalClusterView(simpleDesc(2, instance1, instance2, instance3));
}
@Test
public void testSlowJoiner_suppressionDisabled() throws Exception {
// init just with 1 instance
MiniInstance instance = create(false, false, 1, true, true, null);
// let a slow instance 2 join
try {
instance.getLocalClusterView(simpleDesc(2).activeIds(1, 2));
fail("should complain");
} catch(Exception e) {
// this empty catch is okay
}
}
@Test
public void testSlowJoiner() throws Exception {
// init just with 1 instance
MiniInstance instance1 = create(1, true, true, null);
assertNotNull(instance1.getLocalClusterView(simpleDesc(1).me(instance1.me).activeIds(instance1.me)));
instance1.storeSyncToken("1");
// let a slow instance 2 join
// this should result in a view with just instance 1 - so excludes slow instance 2 for now
LocalClusterView localView = instance1.getLocalClusterView(simpleDesc(2).activeIds(1, 2));
assertNotNull(localView);
assertPartiallyStarted(localView, 2);
assertEquals(1, localView.getInstances().size());
assertNotNull(localView.getLocalInstance());
assertEquals(instance1.slingId, localView.getLocalInstance().getSlingId());
}
@Test
public void testFastJoiner() throws Exception {
// init just with 1 instance
MiniInstance instance1 = create(1, true, true, null);
assertNotNull(instance1.getLocalClusterView(simpleDesc(1).me(instance1.me).activeIds(instance1.me)));
instance1.storeSyncToken("1");
// let a fast instance 2 join
// but first don't store the syncToken yet
MiniInstance instance2 = create(2, true, true, null);
LocalClusterView localView = instance1.getLocalClusterView(simpleDesc(2, instance1, instance2));
assertNotNull(localView);
assertPartiallyStarted(localView, 2);
assertEquals(1, localView.getInstances().size());
// only store it now - and now it should have joined
instance2.storeSyncToken("2");
localView = instance1.getLocalClusterView(simpleDesc(2, instance1, instance2));
assertNotNull(localView);
assertNoPartiallyStarted(localView);
assertEquals(2, localView.getInstances().size());
}
@Test
public void testJoineLeaveMultiple() throws Exception {
MiniInstance instance1 = create(1, true, true, null);
assertNotNull(instance1.getLocalClusterView(simpleDesc(1).me(instance1.me).activeIds(instance1.me)));
instance1.storeSyncToken("1");
assertNotNull(instance1.getLocalClusterView(simpleDesc(1).me(instance1.me).activeIds(instance1.me)));
// let a fast instance 2 join
MiniInstance instance2 = create(2, true, true, "2");
LocalClusterView localView = instance1.getLocalClusterView(simpleDesc(2, instance1, instance2));
assertNotNull(localView);
assertNoPartiallyStarted(localView);
assertEquals(2, localView.getInstances().size());
instance1.storeSyncToken("2");
// from 2's point of view
localView = instance2.getLocalClusterView(simpleDesc(2, instance1, instance2));
assertNotNull(localView);
assertNoPartiallyStarted(localView);
assertEquals(2, localView.getInstances().size());
// leave 2
localView = instance1.getLocalClusterView(simpleDesc(3, instance1));
instance1.storeSyncToken("3");
assertNotNull(localView);
assertNoPartiallyStarted(localView);
assertEquals(1, localView.getInstances().size());
// slow join 3
localView = instance1.getLocalClusterView(simpleDesc(4, instance1, new Identifiable(3)));
instance1.storeSyncToken("4");
assertNotNull(localView);
assertEquals(1, localView.getInstances().size());
assertPartiallyStarted(localView, 3);
// rejoin 2
instance1.storeSyncToken("5");
instance2.storeSyncToken("5");
localView = instance1.getLocalClusterView(simpleDesc(5, instance1, instance2, new Identifiable(3)));
assertNotNull(localView);
assertPartiallyStarted(localView, 3);
assertEquals(2, localView.getInstances().size());
// from 2's point of view
localView = instance2.getLocalClusterView(simpleDesc(5, instance1, instance2, new Identifiable(3)));
assertNotNull(localView);
assertPartiallyStarted(localView, 3);
assertEquals(2, localView.getInstances().size());
MiniInstance instance3 = create(3, true, true, "6");
localView = instance1.getLocalClusterView(simpleDesc(6, instance1, instance2, instance3));
assertNotNull(localView);
assertNoPartiallyStarted(localView);
assertEquals(3, localView.getInstances().size());
}
@Test
public void testRejoin_reuseClusterNodeId_newSlingId() throws Exception {
MiniInstance instance1 = create(1, true, true, "1");
MiniInstance instance2 = create(2, true, true, "1");
LocalClusterView localView;
localView = instance1.getLocalClusterView(simpleDesc(1, instance1, instance2));
assertEquals(2, localView.getInstances().size());
localView = instance1.getLocalClusterView(simpleDesc(2, instance1));
assertEquals(1, localView.getInstances().size());
instance1.storeSyncToken("2");
localView = instance1.getLocalClusterView(simpleDesc(2, instance1));
assertEquals(1, localView.getInstances().size());
// instance2b starts up, reusing the clusterNodeId - but not sling level modifications yet - stealth
MiniInstance instance2b = withNewSlingId(instance2);
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
// storing the slingId doesn't change anything, it was already considered as not previously seen, hence suppressed
instance2b.storeSlingId();
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
// same with the leaderElectiOnId
instance2b.initLeaderElectionId();
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
// but once the syncToken is set, it would rejoin
instance2b.storeSyncToken("3");
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(2, localView.getInstances().size());
}
@Test
public void testRejoin_newClusterNodeId_reuseSlingId_slow() throws Exception {
MiniInstance instance1 = create(1, true, true, "1");
MiniInstance instance2 = create(2, true, true, "1");
LocalClusterView localView;
localView = instance1.getLocalClusterView(simpleDesc(1, instance1, instance2));
assertEquals(2, localView.getInstances().size());
localView = instance1.getLocalClusterView(simpleDesc(2, instance1));
assertEquals(1, localView.getInstances().size());
instance1.storeSyncToken("2");
localView = instance1.getLocalClusterView(simpleDesc(2, instance1));
assertEquals(1, localView.getInstances().size());
// instance2b starts up, new clusterNodeId
MiniInstance instance2b = withSlingId(new Identifiable(3), instance2.slingId);
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
// storing slingId doesnt' change anything - still suppressed
instance2b.storeSlingId();
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
// storing the leaderElectiOnId still doesn't change anything
instance2b.initLeaderElectionId();
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
// with the wrong syncToken it is still not in
instance2b.storeSyncToken("0");
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
instance2b.storeSyncToken("1");
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
instance2b.storeSyncToken("2");
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
// only once the correct syncToken is set, it would rejoin
instance2b.storeSyncToken("3");
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2b));
assertEquals(2, localView.getInstances().size());
}
@Test
public void testRejoin_newClusterNodeId_reuseSlingId_fast() throws Exception {
MiniInstance instance1 = create(1, true, true, "1");
MiniInstance instance2 = create(2, true, true, "1");
LocalClusterView localView;
localView = instance1.getLocalClusterView(simpleDesc(1, instance1, instance2));
assertEquals(2, localView.getInstances().size());
// instance2 silently crashes
// instance2b starts up, new clusterNodeId, same slingId, stored immediately
MiniInstance instance2b = withSlingId(new Identifiable(3), instance2.slingId);
instance2b.storeSlingId();
instance2b.initLeaderElectionId();
localView = instance1.getLocalClusterView(simpleDesc(2, instance1, instance2b));
assertEquals(1, localView.getInstances().size());
instance2b.storeSyncToken("2");
localView = instance1.getLocalClusterView(simpleDesc(2, instance1, instance2b));
assertEquals(2, localView.getInstances().size());
}
@Test
public void testMultipleSlowJoins() throws Exception {
LocalClusterView localView;
// instance 1 starts up normal, fast
MiniInstance instance1 = create(1, true, true, "1");
localView = instance1.getLocalClusterView(simpleDesc(1, instance1));
assertEquals(1, localView.getInstances().size());
// instance 2 is slow
MiniInstance instance2 = create(2, false, false, null);
localView = instance1.getLocalClusterView(simpleDesc(2, instance1, instance2));
assertEquals(1, localView.getInstances().size());
instance1.storeSyncToken("2");
localView = instance1.getLocalClusterView(simpleDesc(2, instance1, instance2));
assertEquals(1, localView.getInstances().size());
// instance 3 is slow
MiniInstance instance3 = create(3, false, false, null);
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2, instance2));
assertEquals(1, localView.getInstances().size());
instance1.storeSyncToken("3");
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2, instance3));
assertEquals(1, localView.getInstances().size());
// instance 3 finishes startup, almost
instance3.storeSlingId();
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2, instance3));
assertEquals(1, localView.getInstances().size());
instance3.initLeaderElectionId();
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2, instance3));
assertEquals(1, localView.getInstances().size());
// instance 2 also finishes startup, almost
instance2.storeSlingId();
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2, instance3));
assertEquals(1, localView.getInstances().size());
instance2.initLeaderElectionId();
localView = instance1.getLocalClusterView(simpleDesc(3, instance1, instance2, instance3));
assertEquals(1, localView.getInstances().size());
// instance 4 is slow
MiniInstance instance4 = create(4, false, false, null);
localView = instance1.getLocalClusterView(simpleDesc(4, instance1, instance2, instance3, instance4));
assertEquals(1, localView.getInstances().size());
instance1.storeSyncToken("4");
localView = instance1.getLocalClusterView(simpleDesc(4, instance1, instance2, instance3, instance4));
assertEquals(1, localView.getInstances().size());
instance3.storeSyncToken("3");
localView = instance1.getLocalClusterView(simpleDesc(4, instance1, instance2, instance3, instance4));
assertEquals(1, localView.getInstances().size());
instance3.storeSyncToken("4");
localView = instance1.getLocalClusterView(simpleDesc(4, instance1, instance2, instance3, instance4));
assertEquals(2, localView.getInstances().size());
assertEqualList(localView.getInstances(), instance1, instance3);
// instance 5 is slow
MiniInstance instance5 = create(5, false, false, null);
localView = instance1.getLocalClusterView(simpleDesc(5, instance1, instance2, instance3, instance4, instance5));
assertEquals(2, localView.getInstances().size());
assertEqualList(localView.getInstances(), instance1, instance3);
instance1.storeSyncToken("5");
localView = instance1.getLocalClusterView(simpleDesc(5, instance1, instance2, instance3, instance4, instance5));
assertEquals(2, localView.getInstances().size());
assertEqualList(localView.getInstances(), instance1, instance3);
instance4.storeSyncToken("5");
instance4.initLeaderElectionId();
instance4.storeSlingId();
localView = instance1.getLocalClusterView(simpleDesc(5, instance1, instance2, instance3, instance4, instance5));
assertEqualList(localView.getInstances(), instance1, instance3, instance4);
assertEquals(3, localView.getInstances().size());
instance2.storeSyncToken("3");
instance2.initLeaderElectionId();
instance2.storeSlingId();
localView = instance1.getLocalClusterView(simpleDesc(5, instance1, instance2, instance3, instance4, instance5));
assertEqualList(localView.getInstances(), instance1, instance3, instance4);
assertEquals(3, localView.getInstances().size());
instance2.storeSyncToken("5");
localView = instance1.getLocalClusterView(simpleDesc(5, instance1, instance2, instance3, instance4, instance5));
assertEquals(4, localView.getInstances().size());
assertEqualList(localView.getInstances(), instance1, instance3, instance4, instance2);
instance5.storeSyncToken("5");
instance5.initLeaderElectionId();
instance5.storeSlingId();
localView = instance1.getLocalClusterView(simpleDesc(5, instance1, instance2, instance3, instance4, instance5));
assertEquals(5, localView.getInstances().size());
assertEqualList(localView.getInstances(), instance1, instance3, instance4, instance2, instance5);
}
private void assertEqualList(List<InstanceDescription> actualInstances,
MiniInstance... expectedInstances) {
assertEquals(actualInstances.size(), expectedInstances.length);
Set<InstanceDescription> s = new HashSet<>(actualInstances);
Iterator<InstanceDescription> it = s.iterator();
out: while(it.hasNext()) {
InstanceDescription n = it.next();
StringBuffer sb = new StringBuffer();
for (MiniInstance i : expectedInstances) {
if (n.getSlingId() == i.slingId) {
it.remove();
break out;
}
sb.append(i + ", ");
}
fail("instance not found: slingId=" + n.getSlingId() + " in list " + sb);
}
}
@Test
public void testCrashDuringJoin() throws Exception {
LocalClusterView localView;
MiniInstance instance1 = create(1, true, true, "1");
localView = instance1.getLocalClusterView(simpleDesc(1, instance1));
assertEquals(1, localView.getInstances().size());
MiniInstance instance2 = create(2, false, false, null);
localView = instance1.getLocalClusterView(simpleDesc(1, instance1, instance2));
assertEquals(1, localView.getInstances().size());
MiniInstance instance3 = create(3, false, false, null);
localView = instance1.getLocalClusterView(simpleDesc(1, instance1, instance2, instance3));
assertEquals(1, localView.getInstances().size());
localView = instance1.getLocalClusterView(simpleDesc(1, instance1, instance2));
assertEquals(1, localView.getInstances().size());
localView = instance1.getLocalClusterView(simpleDesc(1, instance1));
assertEquals(1, localView.getInstances().size());
}
@Test
public void testLocalInstanceNotInView() throws Exception {
MiniInstance instance1 = create(1, true, true, "1");
MiniInstance instance2 = create(2, true, true, "1");
try {
instance1.getLocalClusterView(simpleDesc(1, instance2));
fail("should complain");
} catch (Exception e) {
// this empty catch is okay
}
}
@Test
public void testSuppressionTimeout() throws Exception {
Config config = new Config() {
@Override
public long getClusterSyncServiceIntervalMillis() {
return 9999;
}
@Override
public boolean getSuppressPartiallyStartedInstances() {
return true;
}
@Override
public boolean getSyncTokenEnabled() {
return true;
}
@Override
public long getSuppressionTimeoutSeconds() {
return 1;
}
};
MiniInstance instance1 = new MiniInstance(1, UUID.randomUUID().toString(), nodeStore, config);
instance1.storeSlingId();
instance1.initLeaderElectionId();
instance1.storeSyncToken("1");
LocalClusterView localView;
MiniInstance instance2 = create(2, false, false, null);
localView = instance1.getLocalClusterView(simpleDesc(1, instance1, instance2));
assertEquals(1, localView.getInstances().size());
Thread.sleep(1250);
try {
instance1.getLocalClusterView(simpleDesc(1, instance1, instance2));
fail("should have failed");
} catch(Exception e) {
// this empty catch is okay
}
instance2.storeSlingId();
try {
instance1.getLocalClusterView(simpleDesc(1, instance1, instance2));
fail("should have failed");
} catch(Exception e) {
// this empty catch is okay
}
instance2.initLeaderElectionId();
localView = instance1.getLocalClusterView(simpleDesc(1, instance1, instance2));
assertEquals(2, localView.getInstances().size());
}
}