/**
* Copyright 2009 the original author or authors.
*
* Licensed 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 net.sf.katta.client;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import net.sf.katta.AbstractTest;
import net.sf.katta.client.WorkQueue.INodeInteractionFactory;
import org.apache.hadoop.ipc.VersionedProtocol;
import org.apache.log4j.Logger;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Test for {@link WorkQueue}.
*/
public class WorkQueueTest extends AbstractTest {
@SuppressWarnings("unused")
private static final Logger LOG = Logger.getLogger(WorkQueueTest.class);
@Test
public void testWorkQueue() throws Exception {
TestShardManager sm = new TestShardManager();
Method method = TestServer.class.getMethod("doSomething", Integer.TYPE);
WorkQueue.resetInstanceCounter();
for (int i = 0; i < 500; i++) {
sm.reset();
TestNodeInteractionFactory factory = new TestNodeInteractionFactory(10);
WorkQueue<Integer> wq = new WorkQueue<Integer>(factory, sm, sm.allShards(), method, -1, 16);
assertEquals(String.format("WorkQueue[TestServer.doSomething(16) (id=%d)]", i), wq.toString());
Map<String, List<String>> plan = sm.createNode2ShardsMap(sm.allShards());
for (String node : plan.keySet()) {
wq.execute(node, plan, 1, 3);
}
ClientResult<Integer> r = wq.getResults(1000);
int numNodes = plan.keySet().size();
int numShards = sm.allShards().size();
assertEquals(String.format("ClientResult: %d results, 0 errors, %d/%d shards (closed) (complete)", numNodes,
numShards, numShards), r.toString());
assertEquals(6, factory.getCalls().size());
wq.shutdown();
}
}
@Test
public void testSubmitAfterShutdown() throws Exception {
TestNodeInteractionFactory factory = new TestNodeInteractionFactory(10);
TestShardManager sm = new TestShardManager();
Method method = TestServer.class.getMethod("doSomething", Integer.TYPE);
WorkQueue.resetInstanceCounter();
WorkQueue<Integer> wq = new WorkQueue<Integer>(factory, sm, sm.allShards(), method, -1, 16);
ClientResult<Integer> r = wq.getResults(0, false);
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards", r.toString());
wq.shutdown();
r = wq.getResults(0, false);
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards (closed)", r.toString());
Map<String, List<String>> plan = sm.createNode2ShardsMap(sm.allShards());
for (String node : plan.keySet()) {
wq.execute(node, plan, 1, 3);
}
r = wq.getResults(0, false);
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards (closed)", r.toString());
assertEquals(0, factory.getCalls().size());
}
@Test
public void testSubmitAfterClose() throws Exception {
TestNodeInteractionFactory factory = new TestNodeInteractionFactory(10);
TestShardManager sm = new TestShardManager();
Method method = TestServer.class.getMethod("doSomething", Integer.TYPE);
WorkQueue.resetInstanceCounter();
WorkQueue<Integer> wq = new WorkQueue<Integer>(factory, sm, sm.allShards(), method, -1, 16);
ClientResult<Integer> r = wq.getResults(0, false);
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards", r.toString());
r.close();
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards (closed)", r.toString());
r = wq.getResults(0, false);
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards (closed)", r.toString());
Map<String, List<String>> plan = sm.createNode2ShardsMap(sm.allShards());
for (String node : plan.keySet()) {
wq.execute(node, plan, 1, 3);
}
r = wq.getResults(0, false);
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards (closed)", r.toString());
assertEquals(0, factory.getCalls().size());
}
@Test
public void testGetResultTimeout() throws Exception {
TestShardManager sm = new TestShardManager();
Method method = TestServer.class.getMethod("doSomething", Integer.TYPE);
WorkQueue.resetInstanceCounter();
TestNodeInteractionFactory factory = new TestNodeInteractionFactory(10);
factory.additionalSleepTime = 60000;
WorkQueue<Integer> wq = new WorkQueue<Integer>(factory, sm, sm.allShards(), method, -1, 16);
Map<String, List<String>> plan = sm.createNode2ShardsMap(sm.allShards());
for (String node : plan.keySet()) {
wq.execute(node, plan, 1, 3);
}
int numShards = sm.allShards().size();
long slop = 20;
// No delay
long t1 = System.currentTimeMillis();
ClientResult<Integer> r = wq.getResults(0, false);
long t2 = System.currentTimeMillis();
assertEquals(String.format("ClientResult: 0 results, 0 errors, 0/%d shards", numShards), r.toString());
assertTrue(t2 - t1 < slop);
// Short delay
t1 = System.currentTimeMillis();
r = wq.getResults(500, false);
t2 = System.currentTimeMillis();
assertEquals(String.format("ClientResult: 0 results, 0 errors, 0/%d shards", numShards), r.toString());
assertTrue(t2 - t1 >= 500);
assertTrue(t2 - t1 < 500 + slop);
// Tiny delay.
t1 = System.currentTimeMillis();
r = wq.getResults(10, false);
t2 = System.currentTimeMillis();
assertEquals(String.format("ClientResult: 0 results, 0 errors, 0/%d shards", numShards), r.toString());
assertTrue(t2 - t1 >= 10);
assertTrue(t2 - t1 < 10 + slop);
// Stop soon.
t1 = System.currentTimeMillis();
r = wq.getResults(100, true);
t2 = System.currentTimeMillis();
assertEquals(String.format("ClientResult: 0 results, 0 errors, 0/%d shards (closed)", numShards), r.toString());
assertTrue(t2 - t1 >= 100);
assertTrue(t2 - t1 < 100 + slop);
wq.shutdown();
}
// Does user calling close() wake up the work queue?
@Test
public void testUserCloseEvent() throws Exception {
for (IResultPolicy<String> policy : new IResultPolicy[] { new ResultCompletePolicy<String>(4000),
new ResultCompletePolicy<String>(4000, true), new ResultCompletePolicy<String>(4000, false),
new ResultCompletePolicy<String>(50, 950, 0.99, true),
new ResultCompletePolicy<String>(950, 50, 0.99, true),
new ResultCompletePolicy<String>(50, 950, 0.99, false),
new ResultCompletePolicy<String>(950, 50, 0.99, false) }) {
INodeInteractionFactory<String> factory = nullFactory();
TestShardManager sm = new TestShardManager();
Method method = Object.class.getMethod("toString");
WorkQueue.resetInstanceCounter();
WorkQueue<String> wq = new WorkQueue<String>(factory, sm, sm.allShards(), method, -1);
final ClientResult<String> result = wq.getResults(0, false);
assertFalse(result.isClosed());
sleep(10);
/*
* Simulate the user polling then eventually closing the result.
*/
final long start = System.currentTimeMillis();
new Thread(new Runnable() {
public void run() {
sleep(100);
result.close();
}
}).start();
/*
* Now block on results.
*/
ClientResult<String> result2 = wq.getResults(policy);
long time = System.currentTimeMillis() - start;
//
if (time <= 50 || time >= 200) {
System.err.println("Took " + time + ", expected 100. Policy = " + policy);
}
assertTrue(time > 50);
assertTrue(time < 200);
assertTrue(result2.isClosed());
wq.shutdown();
}
}
// Does IResultPolicy calling close() wake up the work queue?
@Test(timeout = 10000)
public void testPolicyCloseEvent() throws Exception {
INodeInteractionFactory<String> factory = nullFactory();
TestShardManager sm = new TestShardManager();
Method method = Object.class.getMethod("toString");
WorkQueue.resetInstanceCounter();
IResultPolicy<String> policy = new IResultPolicy<String>() {
private long now = System.currentTimeMillis();
private long closeTime = now + 100;
private long stopTime = now + 1000;
public long waitTime(ClientResult<String> result) {
final long innerNow = System.currentTimeMillis();
if (innerNow >= closeTime) {
result.close();
}
if (innerNow >= stopTime) {
return 0;
} else if (innerNow >= closeTime) {
return stopTime - innerNow;
} else {
return closeTime - innerNow;
}
}
};
WorkQueue<String> wq = new WorkQueue<String>(factory, sm, sm.allShards(), method, -1);
sleep(10);
long startTime = System.currentTimeMillis();
ClientResult<String> result = wq.getResults(policy);
long time = System.currentTimeMillis() - startTime;
assertTrue(result.isClosed());
//
if (time <= 50 || time >= 200) {
System.err.println("Took " + time + ", expected 100. Policy = " + policy);
}
assertTrue(time > 50);
assertTrue(time < 200);
wq.shutdown();
}
@Test
public void testPolling() throws Exception {
TestShardManager sm = new TestShardManager(null, 80, 1);
Method method = TestServer.class.getMethod("doSomething", Integer.TYPE);
WorkQueue.resetInstanceCounter();
TestNodeInteractionFactory factory = new TestNodeInteractionFactory(2500);
WorkQueue<Integer> wq = new WorkQueue<Integer>(factory, sm, sm.allShards(), method, -1, 16);
Map<String, List<String>> plan = sm.createNode2ShardsMap(sm.allShards());
ClientResult<Integer> r = wq.getResults(0, false);
System.out.println("Expected graph:");
for (int len : new int[] { 0, 6, 12, 16, 23, 34, 40, 50, 51, 58, 64, 68, 76, 80 }) {
bar(len);
}
System.out.println("Progress:");
for (String node : plan.keySet()) {
wq.execute(node, plan, 1, 3);
}
double coverage = 0.0;
do {
coverage = r.getShardCoverage();
int len = (int) Math.round(coverage * 80);
bar(len);
if (coverage < 1.0) {
sleep(200);
}
} while (coverage < 1.0);
System.out.println("Done.");
wq.shutdown();
}
private void bar(int len) {
StringBuilder sb = new StringBuilder();
sb.append('|');
for (int i = 0; i < 80; i++) {
sb.append(i < len ? '#' : ' ');
}
sb.append('|');
System.out.println(sb);
}
public interface ProxyProvider {
public VersionedProtocol getProxy(String node);
}
public static class TestShardManager implements INodeProxyManager {
private int numNodes;
private int replication;
private List<String> allNodes;
private Set<String> allShards;
private Map<String, List<String>> shardMap;
private INodeSelectionPolicy _selectionPolicy;
private ProxyProvider proxyProvider;
private boolean shardMapsFail = false;
public TestShardManager() {
this(null, 8, 3);
}
public TestShardManager(ProxyProvider proxyProvider, int numNodes, int replication) {
this.proxyProvider = proxyProvider;
this.numNodes = numNodes;
this.replication = replication;
reset();
}
public void reset() {
// Nodes n1, n2, n3...
String[] nodes = new String[numNodes];
for (int i = 0; i < numNodes; i++) {
nodes[i] = "n" + (i + 1);
}
allNodes = Arrays.asList(nodes);
// Shards s1, s3, s3... (same # as nodes)
String[] shards = new String[numNodes];
for (int i = 0; i < numNodes; i++) {
shards[i] = "s" + (i + 1);
}
allShards = new HashSet<String>(Arrays.asList(shards));
// Node i has shards i, i+1, i+2... depending on replication level.
shardMap = new HashMap<String, List<String>>();
for (int i = 0; i < numNodes; i++) {
List<String> shardList = new ArrayList<String>();
for (int j = 0; j < replication; j++) {
shardList.add(shards[(i + j) % numNodes]);
}
shardMap.put(nodes[i], shardList);
}
// Compute reverse map.
_selectionPolicy = new BasicNodeSelectionPolicy();
for (int i = 0; i < numNodes; i++) {
String thisShard = shards[i];
List<String> nodeList = new ArrayList<String>();
for (int j = 0; j < numNodes; j++) {
if (shardMap.get(nodes[j]).contains(thisShard)) {
nodeList.add(nodes[j]);
}
}
_selectionPolicy.update(thisShard, nodeList);
}
shardMapsFail = false;
}
public void setShardMapsFail(boolean shardMapsFail) {
this.shardMapsFail = shardMapsFail;
}
public Map<String, List<String>> createNode2ShardsMap(Collection<String> shards) throws ShardAccessException {
if (shardMapsFail) {
throw new ShardAccessException("Test error");
}
return Collections.unmodifiableMap(_selectionPolicy.createNode2ShardsMap(shards));
}
public VersionedProtocol getProxy(String node, boolean establishIfNotExists) {
return proxyProvider != null ? proxyProvider.getProxy(node) : null;
}
public void reportNodeCommunicationFailure(String node, Throwable t) {
_selectionPolicy.removeNode(node);
}
public List<String> allNodes() {
return Collections.unmodifiableList(allNodes);
}
public Set<String> allShards() {
return Collections.unmodifiableSet(allShards);
}
public Map<String, List<String>> getMap() {
return Collections.unmodifiableMap(shardMap);
}
@Override
public void shutdown() {
}
@Override
public void reportNodeCommunicationSuccess(String node) {
}
}
public static class TestNodeInteractionFactory implements INodeInteractionFactory<Integer> {
public class Entry {
public String node;
public Method method;
public Object[] args;
public Entry(String node, Method method, Object[] args) {
this.node = node;
this.method = method;
this.args = args;
}
@Override
public String toString() {
return node + ":" + method.getName() + ":" + Arrays.asList(args).toString();
}
}
public List<Entry> calls = new ArrayList<Entry>();
public int maxSleep;
public long additionalSleepTime = 0; // TODO combine sleeps
public TestNodeInteractionFactory(int maxSleep) {
this.maxSleep = maxSleep;
}
public Runnable createInteraction(Method method, final Object[] args, int shardArrayParamIndex, final String node,
Map<String, List<String>> nodeShardMap, int tryCount, int maxTryCount, INodeProxyManager shardManager,
INodeExecutor nodeExecutor, final IResultReceiver<Integer> results) {
calls.add(new Entry(node, method, args));
final long additionalSleepTime2 = additionalSleepTime;
final TestServer server = new TestServer(maxSleep);
final List<String> shards = nodeShardMap.get(node);
return new Runnable() {
public void run() {
if (additionalSleepTime2 > 0) {
sleep(additionalSleepTime2);
}
int n = ((Integer) args[0]).intValue();
int r = server.doSomething(n);
// System.out.printf("Test interaction, node=%s, f(%d)=%d, shards=%s\n",
// node, n, r, shards);
results.addResult(r, shards);
}
};
}
public List<Entry> getCalls() {
return calls;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
String sep = "";
for (Entry entry : calls) {
sb.append(sep);
sb.append(entry.toString());
sep = ", ";
}
return sb.toString();
}
}
private static class TestServer {
private static Random rand = new Random("testserver".hashCode());
private int maxSleep;
public TestServer(int maxSleep) {
this.maxSleep = maxSleep;
}
public int doSomething(int n) {
long msec = rand.nextInt(maxSleep);
sleep(msec);
return n * 2;
}
}
/** Returns an interaction factory that ignores all calls and does nothing. */
public static <T> INodeInteractionFactory<T> nullFactory() {
return new INodeInteractionFactory<T>() {
public Runnable createInteraction(Method method, Object[] args, int shardArrayParamIndex, String node,
Map<String, List<String>> nodeShardMap, int tryCount, int maxTryCount, INodeProxyManager shardManager,
INodeExecutor nodeExecutor, IResultReceiver<T> results) {
return null;
}
};
}
protected static void sleep(long msec) {
long now = System.currentTimeMillis();
long stop = now + msec;
while (now < stop) {
try {
Thread.sleep(stop - now);
} catch (InterruptedException e) {
// proceed
}
now = System.currentTimeMillis();
}
}
}