/*
* 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.commons.providers.spi.base;
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 java.lang.reflect.Field;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.discovery.commons.providers.DummyTopologyView;
import org.apache.sling.discovery.commons.providers.ViewStateManager;
import org.apache.sling.discovery.commons.providers.base.DummyListener;
import org.apache.sling.discovery.commons.providers.base.TestHelper;
import org.apache.sling.discovery.commons.providers.base.ViewStateManagerFactory;
import org.apache.sling.discovery.commons.providers.spi.base.AbstractServiceWithBackgroundCheck.BackgroundCheckRunnable;
import org.apache.sling.jcr.api.SlingRepository;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestOakSyncTokenService {
private final static Logger logger = LoggerFactory.getLogger(TestOakSyncTokenService.class);
private static final String SYNCTOKEN_PATH = "/var/discovery/commons/synctokens";
private static final String IDMAP_PATH = "/var/discovery/commons/idmap";
public final class SimpleCommonsConfig implements DiscoveryLiteConfig {
private long bgIntervalMillis;
private long bgTimeoutMillis;
SimpleCommonsConfig() {
this(1000, -1); // defaults
}
SimpleCommonsConfig(long bgIntervalMillis, long bgTimeoutMillis) {
this.bgIntervalMillis = bgIntervalMillis;
this.bgTimeoutMillis = bgTimeoutMillis;
}
@Override
public String getSyncTokenPath() {
return SYNCTOKEN_PATH;
}
@Override
public String getIdMapPath() {
return IDMAP_PATH;
}
@Override
public long getClusterSyncServiceTimeoutMillis() {
return bgTimeoutMillis;
}
@Override
public long getClusterSyncServiceIntervalMillis() {
return bgIntervalMillis;
}
}
ResourceResolverFactory factory1;
ResourceResolverFactory factory2;
private SlingRepository repository1;
private SlingRepository repository2;
private MemoryNodeStore memoryNS;
private IdMapService idMapService1;
private String slingId1;
@Before
public void setup() throws Exception {
logger.info("setup: start");
RepositoryTestHelper.resetRepo();
memoryNS = new MemoryNodeStore();
repository1 = RepositoryTestHelper.newOakRepository(memoryNS);
RepositoryTestHelper.initSlingNodeTypes(repository1);
repository2 = RepositoryTestHelper.newOakRepository(memoryNS);
factory1 = RepositoryTestHelper.mockResourceResolverFactory(repository1);
factory2 = RepositoryTestHelper.mockResourceResolverFactory(repository2);
slingId1 = UUID.randomUUID().toString();
idMapService1 = IdMapService.testConstructor(new SimpleCommonsConfig(), new DummySlingSettingsService(slingId1), factory1);
logger.info("setup: end");
}
@After
public void tearDown() throws Exception {
logger.info("teardown: start");
if (repository1!=null) {
RepositoryTestHelper.stopRepository(repository1);
repository1 = null;
}
if (repository2!=null) {
RepositoryTestHelper.stopRepository(repository2);
repository2 = null;
}
logger.info("teardown: end");
}
@Test
public void testOneNode() throws Exception {
logger.info("testOneNode: start");
DummyTopologyView one = TestHelper.newView(true, slingId1, slingId1, slingId1);
Lock lock = new ReentrantLock();
OakBacklogClusterSyncService cs = OakBacklogClusterSyncService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
ViewStateManager vsm = ViewStateManagerFactory.newViewStateManager(lock, cs);
DummyListener l = new DummyListener();
assertEquals(0, l.countEvents());
vsm.bind(l);
cs.triggerBackgroundCheck();
assertEquals(0, l.countEvents());
vsm.handleActivated();
cs.triggerBackgroundCheck();
assertEquals(0, l.countEvents());
vsm.handleNewView(one);
cs.triggerBackgroundCheck();
assertEquals(0, l.countEvents());
cs.triggerBackgroundCheck();
DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().me(1).seq(1).activeIds(1).setFinal(true));
assertTrue(idMapService1.waitForInit(5000));
cs.triggerBackgroundCheck();
assertEquals(0, vsm.waitForAsyncEvents(1000));
assertEquals(1, l.countEvents());
logger.info("testOneNode: end");
}
@Test
public void testTwoNodesOneLeaving() throws Exception {
logger.info("testTwoNodesOneLeaving: start");
String slingId2 = UUID.randomUUID().toString();
DummyTopologyView two1 = TestHelper.newView(true, slingId1, slingId1, slingId1, slingId2);
Lock lock1 = new ReentrantLock();
OakBacklogClusterSyncService cs1 = OakBacklogClusterSyncService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
ViewStateManager vsm1 = ViewStateManagerFactory.newViewStateManager(lock1, cs1);
DummyListener l = new DummyListener();
vsm1.bind(l);
vsm1.handleActivated();
vsm1.handleNewView(two1);
cs1.triggerBackgroundCheck();
assertEquals(0, l.countEvents());
DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(1).activeIds(1).deactivatingIds(2));
cs1.triggerBackgroundCheck();
assertEquals(0, l.countEvents());
// make an assertion that the background runnable is at this stage - even with
// a 2sec sleep - waiting for the deactivating instance to disappear
logger.info("testTwoNodesOneLeaving: sync service should be waiting for backlog to disappear");
Thread.sleep(2000);
BackgroundCheckRunnable backgroundCheckRunnable = cs1.backgroundCheckRunnable;
assertNotNull(backgroundCheckRunnable);
assertFalse(backgroundCheckRunnable.isDone());
assertFalse(backgroundCheckRunnable.cancelled());
// release the deactivating instance by removing it from the clusterView
logger.info("testTwoNodesOneLeaving: freeing backlog - sync service should finish up");
DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(2).activeIds(1));
cs1.triggerBackgroundCheck();
// now give this thing 2 sec to settle
Thread.sleep(2000);
// after that, the backgroundRunnable should be done and no events stuck in vsm
backgroundCheckRunnable = cs1.backgroundCheckRunnable;
assertNotNull(backgroundCheckRunnable);
assertFalse(backgroundCheckRunnable.cancelled());
assertTrue(backgroundCheckRunnable.isDone());
assertEquals(0, vsm1.waitForAsyncEvents(1000));
logger.info("testTwoNodesOneLeaving: setting up 2nd node");
Lock lock2 = new ReentrantLock();
IdMapService idMapService2 = IdMapService.testConstructor(
new SimpleCommonsConfig(), new DummySlingSettingsService(slingId2), factory2);
OakBacklogClusterSyncService cs2 = OakBacklogClusterSyncService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService2, new DummySlingSettingsService(slingId2), factory2);
ViewStateManager vsm2 = ViewStateManagerFactory.newViewStateManager(lock2, cs2);
cs1.triggerBackgroundCheck();
cs2.triggerBackgroundCheck();
assertEquals(1, l.countEvents());
DescriptorHelper.setDiscoveryLiteDescriptor(factory2, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(2).seq(3).activeIds(1, 2));
cs1.triggerBackgroundCheck();
cs2.triggerBackgroundCheck();
assertEquals(1, l.countEvents());
DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(3).activeIds(1, 2));
cs1.triggerBackgroundCheck();
cs2.triggerBackgroundCheck();
assertEquals(1, l.countEvents());
vsm2.handleActivated();
assertTrue(idMapService1.waitForInit(5000));
assertTrue(idMapService2.waitForInit(5000));
DummyTopologyView two2 = TestHelper.newView(two1.getLocalClusterSyncTokenId(), two1.getLocalInstance().getClusterView().getId(), true, slingId1, slingId1, slingId1, slingId2);
vsm2.handleNewView(two2);
cs1.triggerBackgroundCheck();
cs1.triggerBackgroundCheck();
cs2.triggerBackgroundCheck();
cs2.triggerBackgroundCheck();
assertEquals(0, vsm1.waitForAsyncEvents(1000));
assertEquals(1, l.countEvents());
logger.info("testTwoNodesOneLeaving: removing instance2 from the view - even though vsm1 didn't really know about it, it should send a TOPOLOGY_CHANGING - we leave it as deactivating for now...");
DummyTopologyView oneLeaving = two1.clone();
oneLeaving.removeInstance(slingId2);
DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(1).activeIds(1).deactivatingIds(2));
vsm1.handleNewView(oneLeaving);
cs1.triggerBackgroundCheck();
cs2.triggerBackgroundCheck();
// wait for TOPOLOGY_CHANGING to be received by vsm1
assertEquals(0, vsm1.waitForAsyncEvents(5000));
assertEquals(2, l.countEvents());
logger.info("testTwoNodesOneLeaving: marking instance2 as no longer deactivating, so vsm1 should now send a TOPOLOGY_CHANGED");
DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(2).activeIds(1).inactiveIds(2));
cs1.triggerBackgroundCheck();
cs2.triggerBackgroundCheck();
// wait for TOPOLOGY_CHANGED to be received by vsm1
assertEquals(0, vsm1.waitForAsyncEvents(5000));
RepositoryTestHelper.dumpRepo(factory1);
assertEquals(3, l.countEvents());
}
@Test
public void testRapidIdMapServiceActivateDeactivate() throws Exception {
BackgroundCheckRunnable bgCheckRunnable = getBackgroundCheckRunnable(idMapService1);
assertNotNull(bgCheckRunnable);
assertFalse(bgCheckRunnable.isDone());
idMapService1.deactivate();
assertFalse(idMapService1.waitForInit(2500));
bgCheckRunnable = getBackgroundCheckRunnable(idMapService1);
assertNotNull(bgCheckRunnable);
assertTrue(bgCheckRunnable.isDone());
}
private BackgroundCheckRunnable getBackgroundCheckRunnable(IdMapService idMapService) throws NoSuchFieldException, IllegalAccessException {
Field field = idMapService.getClass().getSuperclass().getDeclaredField("backgroundCheckRunnable");
field.setAccessible(true);
Object backgroundCheckRunnable = field.get(idMapService);
return (BackgroundCheckRunnable) backgroundCheckRunnable;
}
}