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