/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Mar 15, 2009
*/
package com.bigdata.service;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import junit.framework.TestCase2;
import com.bigdata.bfs.BigdataFileSystem;
import com.bigdata.btree.IndexMetadata;
import com.bigdata.counters.CounterSet;
import com.bigdata.counters.ICounterSetAccess;
import com.bigdata.io.SerializerUtil;
import com.bigdata.journal.IResourceLockService;
import com.bigdata.journal.ITransactionService;
import com.bigdata.journal.TemporaryStore;
import com.bigdata.mdi.IMetadataIndex;
import com.bigdata.relation.locator.IResourceLocator;
import com.bigdata.service.EventReceiver.EventBTree;
import com.bigdata.service.ndx.IClientIndex;
import com.bigdata.sparse.SparseRowStore;
import com.bigdata.util.DaemonThreadFactory;
import com.bigdata.util.httpd.AbstractHTTPD;
/**
* Unit tests for the {@link EventReceiver}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class TestEventReceiver extends TestCase2 {
/**
*
*/
public TestEventReceiver() {
}
/**
* @param arg0
*/
public TestEventReceiver(String arg0) {
super(arg0);
}
/**
* Subclass overrides {@link #sendEvent()} to send to a configured
* {@link EventReceiver} on the {@link MockFederation}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
static class MyEvent extends Event {
/**
*
*/
private static final long serialVersionUID = 3987546249519888387L;
/**
* @param fed
* @param resource
* @param majorEventType
*/
public MyEvent(IBigdataFederation fed, EventResource resource, Object majorEventType) {
super(fed, resource, majorEventType);
}
protected MyEvent(final IBigdataFederation fed,
final EventResource resource, final Object majorEventType,
final Object minorEventType, final Map<String, Object> details) {
super(fed, resource, majorEventType, minorEventType, details);
}
@Override
public MyEvent newSubEvent(Object minorEventType) {
return new MyEvent(this.fed, this.resource, this.majorEventType,
minorEventType, this.details);
}
/**
* Serializes and then de-serializes the event and sends the
* de-serialized reference to the {@link EventReceiver}. This simulates
* RMI where the receiver always has a different instance for each
* message received.
*/
protected void sendEvent() throws IOException {
final byte[] data = SerializerUtil.serialize(this);
final MyEvent e = (MyEvent) SerializerUtil.deserialize(data);
((MockFederation) fed).eventReceiver.notifyEvent(e);
}
}
/**
* Test dispatch using both {@link Event#start()} and {@link Event#end()}.
*
* @throws InterruptedException
*/
public void test_start_end() throws InterruptedException {
final EventBTree eventBTree = EventBTree.createTransient();
final EventReceiver eventReceiver = new EventReceiver(
1000/* eventHistoryMillis */, eventBTree);
final IBigdataFederation fed = new MockFederation(eventReceiver);
final Event e = new MyEvent(fed, new EventResource("testIndex"),
"testEventType");
assertEquals(0, eventReceiver.eventCache.size());
e.start();
/*
* Verify that the event is now in the receiver's cache.
*/
assertEquals(1, eventReceiver.eventCache.size());
assertTrue(eventReceiver.eventCache.containsKey(e.eventUUID));
assertFalse(eventReceiver.eventCache.get(e.eventUUID).complete);
try {
// wait a bit so that endTime != startTime.
Thread.sleep(200/* ms */);
} finally {
e.end();
}
// the event is still in the cache.
assertEquals(1, eventReceiver.eventCache.size());
/*
* Verify the event as recorded in the B+Tree.
*/
assertEquals(1L, eventReceiver.rangeCount(e.startTime, e.startTime + 1));
assertTrue(eventReceiver.rangeIterator(e.startTime, e.startTime + 1)
.hasNext());
final Event t = eventReceiver.rangeIterator(e.startTime,
e.startTime + 1).next();
assertTrue(t.complete);
assertEquals(e.eventUUID, t.eventUUID);
assertEquals(e.startTime, t.startTime);
assertEquals(e.endTime, t.endTime);
/*
* Verify that the receiver will purge the event from its cache.
*/
// wait until the event is old enough to be purged.
Thread.sleep(eventReceiver.eventHistoryMillis);
// request purge of old events.
eventReceiver.pruneHistory(System.currentTimeMillis());
// event is no longer in the cache.
assertEquals(0, eventReceiver.eventCache.size());
assertFalse(eventReceiver.eventCache.containsKey(e.eventUUID));
}
/**
* Test dispatch using only {@link Event#end()} (instantaneous events).
*
* @throws InterruptedException
*/
public void test_endOnly() throws InterruptedException {
final EventBTree eventBTree = EventBTree.createTransient();
final EventReceiver eventReceiver = new EventReceiver(
1000/* eventHistoryMillis */, eventBTree);
final IBigdataFederation fed = new MockFederation(eventReceiver);
final Event e = new MyEvent(fed, new EventResource("testIndex"),
"testEventType");
assertEquals(0, eventReceiver.eventCache.size());
e.end();
assertEquals(e.startTime, e.endTime);
assertTrue(e.complete);
/*
* Verify that the event is in the receiver's cache.
*/
assertEquals(1, eventReceiver.eventCache.size());
assertTrue(eventReceiver.eventCache.containsKey(e.eventUUID));
assertTrue(eventReceiver.eventCache.get(e.eventUUID).complete);
/*
* Verify the event as recorded in the B+Tree.
*/
assertEquals(1L, eventReceiver.rangeCount(e.startTime, e.startTime+1));
assertTrue(eventReceiver.rangeIterator(e.startTime, e.startTime + 1)
.hasNext());
final Event t = eventReceiver.rangeIterator(e.startTime,
e.startTime + 1).next();
assertTrue(t.complete);
assertEquals(e.eventUUID, t.eventUUID);
assertEquals(e.startTime, t.startTime);
assertEquals(e.endTime, t.endTime);
/*
* Verify that the receiver will purge the event from its cache.
*/
// wait until the event is old enough to be purged.
Thread.sleep(eventReceiver.eventHistoryMillis);
// request purge of old events.
eventReceiver.pruneHistory(System.currentTimeMillis());
// event is no longer in the cache.
assertEquals(0, eventReceiver.eventCache.size());
assertFalse(eventReceiver.eventCache.containsKey(e.eventUUID));
}
/**
* Pumps a bunch of events through and then verifies that the start events
* are correlated with the end events and that the size of the
* {@link LinkedHashMap} is bounded by the #of incomplete events no older
* than the configured eventHistoryMillis.
*
* @throws InterruptedException
*/
public void test_purgesHistory() throws InterruptedException {
final long eventHistoryMillis = 1000L;
final EventBTree eventBTree = EventBTree.createTransient();
final EventReceiver eventReceiver = new EventReceiver(
eventHistoryMillis, eventBTree);
final IBigdataFederation fed = new MockFederation(eventReceiver);
final Random r = new Random();
final long begin = System.currentTimeMillis();
long elapsed;
int nevents = 0;
while ((elapsed = System.currentTimeMillis() - begin) < eventHistoryMillis / 2) {
final Event e = new MyEvent(fed, new EventResource("testIndex"),
"testEventType");
if (r.nextDouble() < .2) {
// instantaneous event.
e.end();
} else {
/*
* event with duration.
*/
e.start();
try {
Thread
.sleep(r.nextInt((int) eventHistoryMillis / 10)/* ms */);
} finally {
e.end();
}
}
nevents++;
}
// all the events should be in the cache.
assertEquals(nevents, eventReceiver.eventCache.size());
// sleep until the events should be expired.
Thread.sleep(eventHistoryMillis);
// prune the event cache using the specified timestamp.
eventReceiver.pruneHistory(System.currentTimeMillis());
// should be empty.
assertEquals(0, eventReceiver.eventCache.size());
}
/**
* Unit tests verifies that the APIs are thread-safe.
*
* @throws InterruptedException
* @throws ExecutionException
*/
public void test_threadSafe() throws InterruptedException, ExecutionException {
final long eventHistoryMillis = 1000L;
final EventBTree eventBTree = EventBTree.createTransient();
final EventReceiver eventReceiver = new EventReceiver(
eventHistoryMillis, eventBTree);
final IBigdataFederation fed = new MockFederation(eventReceiver);
final ExecutorService exService = Executors
.newCachedThreadPool(DaemonThreadFactory.defaultThreadFactory());
final int nthreads = 10;
final int nevents = 100;
try {
final List<Callable<Void>> tasks = new LinkedList<Callable<Void>>();
for (int i = 0; i < nthreads; i++) {
tasks.add(new EventFactory(fed, nevents));
}
tasks.add( new EventConsumer(eventReceiver) );
final List<Future<Void>> futures = exService.invokeAll(tasks);
for(Future f : futures) {
// verify no errors.
f.get();
}
} finally {
exService.shutdownNow();
}
}
/**
* Generates events.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
private static class EventFactory implements Callable<Void> {
private final IBigdataFederation fed;
private final int nevents;
public EventFactory(final IBigdataFederation fed,
final int nevents) {
this.fed = fed;
this.nevents = nevents;
}
public Void call() throws Exception {
final Random r = new Random();
for (int i = 0; i < nevents; i++) {
final Event e = new MyEvent(fed,
new EventResource("testIndex"), "testEventType");
if (r.nextDouble() < .2) {
// instantaneous event.
e.end();
} else {
/*
* event with duration.
*/
e.start();
try {
Thread.sleep(r.nextInt(100)/* ms */);
} catch (InterruptedException ex) {
break;
} finally {
e.end();
}
}
}
return null;
}
}
/**
* Reports on events (places a concurrent read load on the B+Tree on which
* the events are stored).
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
static private class EventConsumer implements Callable<Void> {
final IEventReportingService eventReportingService;
public EventConsumer(final IEventReportingService eventReportingService) {
this.eventReportingService = eventReportingService;
}
public Void call() throws Exception {
for (int i = 0; i < 50; i++) {
eventReportingService.rangeCount(0L, Long.MAX_VALUE);
final Iterator<Event> itr = eventReportingService
.rangeIterator(0L, Long.MAX_VALUE);
while (itr.hasNext()) {
itr.next();
}
Thread.sleep(100/* ms */);
}
return null;
}
}
/**
* Mock federation to support the unit tests in the outer class.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
*/
static class MockFederation implements IBigdataFederation<IEventReceivingService> {
private final IEventReceivingService eventReceiver;
private final UUID serviceUUID = UUID.randomUUID();
MockFederation(final IEventReceivingService eventReceiver) {
this.eventReceiver = eventReceiver;
}
@Override
public IEventReceivingService getService() {
return eventReceiver;
}
@Override
public Class<?> getServiceIface() {
return IEventReceivingService.class;
// return TestEventReceiver.class;
}
@Override
public String getServiceName() {
return IEventReceivingService.class.getName();
// return TestEventReceiver.class.getName();
}
@Override
public UUID getServiceUUID() {
return serviceUUID;
}
/*
* unused methods.
*/
@Override
public void destroy() {
}
@Override
public void dropIndex(String name) {
}
@Override
public IDataService getAnyDataService() {
return null;
}
@Override
public IBigdataClient getClient() {
return null;
}
@Override
public CounterSet getCounters() {
return null;
}
@Override
public IDataService getDataService(UUID serviceUUID) {
return null;
}
@Override
public IDataService getDataServiceByName(String name) {
return null;
}
@Override
public UUID[] getDataServiceUUIDs(int maxCount) {
return null;
}
@Override
public IDataService[] getDataServices(UUID[] uuid) {
return null;
}
@Override
public ExecutorService getExecutorService() {
return null;
}
@Override
public SparseRowStore getGlobalRowStore() {
return null;
}
@Override
public SparseRowStore getGlobalRowStore(final long timstamp) {
return null;
}
@Override
public String getHttpdURL() {
return null;
}
@Override
public IClientIndex getIndex(String name, long timestamp) {
return null;
}
@Override
public long getLastCommitTime() {
return 0;
}
@Override
public ILoadBalancerService getLoadBalancerService() {
return null;
}
@Override
public IMetadataIndex getMetadataIndex(String name, long timestamp) {
return null;
}
@Override
public IMetadataService getMetadataService() {
return null;
}
@Override
public String getServiceCounterPathPrefix() {
return null;
}
@Override
public CounterSet getServiceCounterSet() {
return null;
}
@Override
public ITransactionService getTransactionService() {
return null;
}
@Override
public boolean isDistributed() {
return false;
}
@Override
public boolean isScaleOut() {
return false;
}
@Override
public boolean isStable() {
return false;
}
@Override
public void registerIndex(IndexMetadata metadata) {
}
@Override
public UUID registerIndex(IndexMetadata metadata, UUID dataServiceUUID) {
return null;
}
@Override
public UUID registerIndex(IndexMetadata metadata, byte[][] separatorKeys, UUID[] dataServiceUUIDs) {
return null;
}
@Override
public BigdataFileSystem getGlobalFileSystem() {
return null;
}
@Override
public IResourceLocator<?> getResourceLocator() {
return null;
}
@Override
public IResourceLockService getResourceLockService() {
return null;
}
@Override
public TemporaryStore getTempStore() {
return null;
}
@Override
public void didStart() {
}
@Override
public boolean isServiceReady() {
return false;
}
@Override
public AbstractHTTPD newHttpd(int httpdPort, ICounterSetAccess access) throws IOException {
return null;
}
@Override
public void reattachDynamicCounters() {
}
@Override
public void serviceJoin(IService service, UUID serviceUUID) {
}
@Override
public void serviceLeave(UUID serviceUUID) {
}
@Override
public CounterSet getHostCounterSet() {
return null;
}
@Override
public ScheduledFuture<?> addScheduledTask(Runnable task,
long initialDelay, long delay, TimeUnit unit) {
return null;
}
@Override
public boolean getCollectPlatformStatistics() {
return false;
}
@Override
public boolean getCollectQueueStatistics() {
return false;
}
@Override
public int getHttpdPort() {
return 0;
}
@Override
public Iterator<String> indexNameScan(String prefix, long timestamp) {
return null;
}
@Override
public boolean isGroupCommit() {
return false;
}
@Override
public boolean isJiniFederation() {
//BLZG-1370
return false;
}
}
}