blob: 4a41e7fa2a9bbca98cbd307e0973859fe07f5444 [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.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.discovery.TopologyEvent;
import org.apache.sling.discovery.base.its.setup.OSGiMock;
import org.apache.sling.discovery.base.its.setup.VirtualInstance;
import org.apache.sling.discovery.base.its.setup.mock.DummyResourceResolverFactory;
import org.apache.sling.discovery.base.its.setup.mock.MockFactory;
import org.apache.sling.discovery.commons.providers.base.DummyListener;
import org.apache.sling.discovery.commons.providers.spi.base.DescriptorHelper;
import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteConfig;
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.DummySlingSettingsService;
import org.apache.sling.discovery.commons.providers.spi.base.IdMapService;
import org.apache.sling.discovery.oak.its.setup.OakTestConfig;
import org.apache.sling.discovery.oak.its.setup.OakVirtualInstanceBuilder;
import org.apache.sling.discovery.oak.its.setup.SimulatedLease;
import org.apache.sling.discovery.oak.its.setup.SimulatedLeaseCollection;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OakDiscoveryServiceTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public final class SimpleCommonsConfig implements DiscoveryLiteConfig {
private long bgIntervalMillis;
private long bgTimeoutMillis;
SimpleCommonsConfig(long bgIntervalMillis, long bgTimeoutMillis) {
this.bgIntervalMillis = bgIntervalMillis;
this.bgTimeoutMillis = bgTimeoutMillis;
}
@Override
public String getSyncTokenPath() {
return "/var/synctokens";
}
@Override
public String getIdMapPath() {
return "/var/idmap";
}
@Override
public long getClusterSyncServiceTimeoutMillis() {
return bgTimeoutMillis;
}
@Override
public long getClusterSyncServiceIntervalMillis() {
return bgIntervalMillis;
}
}
@Test
public void testBindBeforeActivate() throws Exception {
OakVirtualInstanceBuilder builder =
(OakVirtualInstanceBuilder) new OakVirtualInstanceBuilder()
.setDebugName("test")
.newRepository("/foo/bar", true);
String slingId = UUID.randomUUID().toString();;
DiscoveryLiteDescriptorBuilder discoBuilder = new DiscoveryLiteDescriptorBuilder();
discoBuilder.id("id").me(1).activeIds(1);
// make sure the discovery-lite descriptor is marked as not final
// such that the view is not already set before we want it to be
discoBuilder.setFinal(false);
DescriptorHelper.setDiscoveryLiteDescriptor(builder.getResourceResolverFactory(),
discoBuilder);
IdMapService idMapService = IdMapService.testConstructor(new SimpleCommonsConfig(1000, -1), new DummySlingSettingsService(slingId), builder.getResourceResolverFactory());
assertTrue(idMapService.waitForInit(2000));
OakDiscoveryService discoveryService = (OakDiscoveryService) builder.getDiscoverService();
assertNotNull(discoveryService);
DummyListener listener = new DummyListener();
for(int i=0; i<100; i++) {
discoveryService.bindTopologyEventListener(listener);
discoveryService.unbindTopologyEventListener(listener);
}
discoveryService.bindTopologyEventListener(listener);
assertEquals(0, listener.countEvents());
discoveryService.activate(null);
assertEquals(0, listener.countEvents());
// some more confusion...
discoveryService.unbindTopologyEventListener(listener);
discoveryService.bindTopologyEventListener(listener);
// only set the final flag now - this makes sure that handlePotentialTopologyChange
// will actually detect a valid new, different view and send out an event -
// exactly as we want to
discoBuilder.setFinal(true);
DescriptorHelper.setDiscoveryLiteDescriptor(builder.getResourceResolverFactory(),
discoBuilder);
// SLING-6924 : need to simulate a OakViewChecker.activate to trigger resetLeaderElectionId
// otherwise no TOPOLOGY_INIT will be generated as without a leaderElectionId we now
// consider a view as NO_ESTABLISHED_VIEW
OSGiMock.activate(builder.getViewChecker());
discoveryService.checkForTopologyChange();
assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
assertEquals(1, listener.countEvents());
discoveryService.unbindTopologyEventListener(listener);
assertEquals(1, listener.countEvents());
discoveryService.bindTopologyEventListener(listener);
assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
assertEquals(2, listener.countEvents()); // should now have gotten an INIT too
}
@Test
public void testDescriptorSeqNumChange() throws Exception {
logger.info("testDescriptorSeqNumChange: start");
OakVirtualInstanceBuilder builder1 =
(OakVirtualInstanceBuilder) new OakVirtualInstanceBuilder()
.setDebugName("instance1")
.newRepository("/foo/barry/foo/", true)
.setConnectorPingInterval(999)
.setConnectorPingTimeout(999);
VirtualInstance instance1 = builder1.build();
OakVirtualInstanceBuilder builder2 =
(OakVirtualInstanceBuilder) new OakVirtualInstanceBuilder()
.setDebugName("instance2")
.useRepositoryOf(instance1)
.setConnectorPingInterval(999)
.setConnectorPingTimeout(999);
VirtualInstance instance2 = builder2.build();
logger.info("testDescriptorSeqNumChange: created both instances, binding listener...");
DummyListener listener = new DummyListener();
OakDiscoveryService discoveryService = (OakDiscoveryService) instance1.getDiscoveryService();
discoveryService.bindTopologyEventListener(listener);
logger.info("testDescriptorSeqNumChange: waiting 2sec, listener should not get anything yet");
assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
assertEquals(0, listener.countEvents());
logger.info("testDescriptorSeqNumChange: issuing 2 heartbeats with each instance should let the topology get established");
instance1.heartbeatsAndCheckView();
instance2.heartbeatsAndCheckView();
instance1.heartbeatsAndCheckView();
instance2.heartbeatsAndCheckView();
logger.info("testDescriptorSeqNumChange: listener should get an event within 2sec from now at latest");
assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
assertEquals(1, listener.countEvents());
ResourceResolverFactory factory = instance1.getResourceResolverFactory();
ResourceResolver resolver = factory.getServiceResourceResolver(null);
instance1.heartbeatsAndCheckView();
assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
assertEquals(1, listener.countEvents());
// increment the seqNum by 2 - simulating a coming and going instance
// while we were sleeping
SimulatedLeaseCollection c = builder1.getSimulatedLeaseCollection();
c.incSeqNum(2);
logger.info("testDescriptorSeqNumChange: incremented seqnum by 2 - issuing another heartbeat should trigger a topology change");
instance1.heartbeatsAndCheckView();
// due to the nature of the syncService/minEventDelay we now explicitly first sleep 2sec before waiting for async events for another 2sec
logger.info("testDescriptorSeqNumChange: sleeping 2sec for topology change to happen");
Thread.sleep(2000);
logger.info("testDescriptorSeqNumChange: ensuring no async events are still in the pipe - for another 2sec");
assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
logger.info("testDescriptorSeqNumChange: now listener should have received 3 events, it got: "+listener.countEvents());
assertEquals(3, listener.countEvents());
}
@Test
public void testNotYetInitializedLeaderElectionid() throws Exception {
logger.info("testNotYetInitializedLeaderElectionid: start");
OakVirtualInstanceBuilder builder1 =
(OakVirtualInstanceBuilder) new OakVirtualInstanceBuilder()
.setDebugName("instance")
.newRepository("/foo/barrx/foo/", true)
.setConnectorPingInterval(999)
.setConnectorPingTimeout(999);
VirtualInstance instance1 = builder1.build();
logger.info("testNotYetInitializedLeaderElectionid: created 1 instance, binding listener...");
DummyListener listener = new DummyListener();
OakDiscoveryService discoveryService = (OakDiscoveryService) instance1.getDiscoveryService();
discoveryService.bindTopologyEventListener(listener);
logger.info("testNotYetInitializedLeaderElectionid: waiting 2sec, listener should not get anything yet");
assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
assertEquals(0, listener.countEvents());
logger.info("testNotYetInitializedLeaderElectionid: issuing 2 heartbeats with each instance should let the topology get established");
instance1.heartbeatsAndCheckView();
instance1.heartbeatsAndCheckView();
logger.info("testNotYetInitializedLeaderElectionid: listener should get an event within 2sec from now at latest");
assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
assertEquals(1, listener.countEvents());
SimulatedLeaseCollection c = builder1.getSimulatedLeaseCollection();
String secondSlingId = UUID.randomUUID().toString();
final SimulatedLease newIncomingInstance = new SimulatedLease(instance1.getResourceResolverFactory(), c, secondSlingId);
c.hooked(newIncomingInstance);
c.incSeqNum(1);
newIncomingInstance.updateLeaseAndDescriptor(new OakTestConfig());
logger.info("testNotYetInitializedLeaderElectionid: issuing another 2 heartbeats");
instance1.heartbeatsAndCheckView();
instance1.heartbeatsAndCheckView();
// there are different properties that an instance must set in the repository such that it finally becomes visible.
// these include:
// 1) idmap : it must map the oak id to sling id
// 2) node named after its own slingId under /var/discovery/oak/clusterInstances/<slingId>
// 3) store the leaderElectionId under /var/discovery/oak/clusterInstances/<slingId>
// in all 3 cases the code must work fine if that node/property doesn't exist
// and that's exactly what we're testing here.
// initially not even the idmap is updated, so we're stuck with TOPOLOGY_CHANGING
// due to the nature of the syncService/minEventDelay we now explicitly first sleep 2sec before waiting for async events for another 2sec
logger.info("testNotYetInitializedLeaderElectionid: sleeping 2sec for topology change to happen");
Thread.sleep(2000);
logger.info("testNotYetInitializedLeaderElectionid: ensuring no async events are still in the pipe - for another 2sec");
assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
logger.info("testNotYetInitializedLeaderElectionid: now listener should have received 2 events, INIT and CHANGING, it got: "+listener.countEvents());
assertEquals(2, listener.countEvents());
List<TopologyEvent> events = listener.getEvents();
assertEquals(TopologyEvent.Type.TOPOLOGY_INIT, events.get(0).getType());
assertEquals(TopologyEvent.Type.TOPOLOGY_CHANGING, events.get(1).getType());
// let's update the idmap first then
DummyResourceResolverFactory factory1 = (DummyResourceResolverFactory) instance1.getResourceResolverFactory();
ResourceResolverFactory factory2 = MockFactory.mockResourceResolverFactory(factory1.getSlingRepository());
ResourceResolver resourceResolver = getResourceResolver(instance1.getResourceResolverFactory());
DiscoveryLiteDescriptor descriptor =
DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
resourceResolver.close();
DiscoveryLiteDescriptorBuilder dlb = prefill(descriptor);
dlb.me(2);
DescriptorHelper.setDiscoveryLiteDescriptor(factory2, dlb);
IdMapService secondIdMapService = IdMapService.testConstructor((DiscoveryLiteConfig) builder1.getConnectorConfig(), new DummySlingSettingsService(secondSlingId), factory2);
instance1.heartbeatsAndCheckView();
instance1.heartbeatsAndCheckView();
Thread.sleep(2000);
assertEquals(2, listener.countEvents());
// now let's add the /var/discovery/oak/clusterInstances/<slingId> node
resourceResolver = getResourceResolver(factory2);
Resource clusterInstancesRes = resourceResolver.getResource(builder1.getConnectorConfig().getClusterInstancesPath());
assertNull(clusterInstancesRes.getChild(secondSlingId));
resourceResolver.create(clusterInstancesRes, secondSlingId, null);
resourceResolver.commit();
assertNotNull(clusterInstancesRes.getChild(secondSlingId));
resourceResolver.close();
instance1.heartbeatsAndCheckView();
instance1.heartbeatsAndCheckView();
Thread.sleep(2000);
assertEquals(2, listener.countEvents());
// now let's add the leaderElectionId
resourceResolver = getResourceResolver(factory2);
Resource instanceResource = resourceResolver.getResource(builder1.getConnectorConfig().getClusterInstancesPath() + "/" + secondSlingId);
assertNotNull(instanceResource);
instanceResource.adaptTo(ModifiableValueMap.class).put("leaderElectionId", "0");
resourceResolver.commit();
resourceResolver.close();
instance1.heartbeatsAndCheckView();
instance1.heartbeatsAndCheckView();
Thread.sleep(2000);
assertEquals(3, listener.countEvents());
assertEquals(TopologyEvent.Type.TOPOLOGY_CHANGED, events.get(2).getType());
}
private DiscoveryLiteDescriptorBuilder prefill(DiscoveryLiteDescriptor d) throws Exception {
DiscoveryLiteDescriptorBuilder b = new DiscoveryLiteDescriptorBuilder();
b.setFinal(true);
long seqnum = d.getSeqNum();
b.seq((int) seqnum);
b.activeIds(box(d.getActiveIds()));
b.deactivatingIds(box(d.getDeactivatingIds()));
b.me(d.getMyId());
b.id(d.getViewId());
return b;
}
private Integer[] box(final int[] ids) {
//TODO: use Guava
List<Integer> list = new ArrayList<Integer>(ids.length);
for (Integer i : ids) {
list.add(i);
}
return list.toArray(new Integer[list.size()]);
}
private ResourceResolver getResourceResolver(ResourceResolverFactory resourceResolverFactory) throws LoginException {
return resourceResolverFactory.getServiceResourceResolver(null);
}
}