/**
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.katta.AbstractTest;
import org.apache.hadoop.ipc.VersionedProtocol;
import org.junit.Before;
import org.junit.Test;
/**
* Test for {@link NodeInteraction}.
*/
public class NodeInteractionTest extends AbstractTest {
private TestProxyProvider _pp;
private WorkQueueTest.TestShardManager _sm;
private TestNodeExecutor _ne;
private Map<String, List<String>> _map;
@Before
public void setUp() throws Exception {
_pp = new TestProxyProvider();
_sm = new WorkQueueTest.TestShardManager(_pp, 8, 3);
_ne = new TestNodeExecutor();
_map = _sm.createNode2ShardsMap(_sm.allShards());
}
@Test
public void testNormalCall() throws Exception {
Method method = ITestServer.class.getMethod("testMethod", String.class, String[].class);
Object[] args = new Object[] { "foo", null };
ClientResult<String> r = new ClientResult<String>(null, _sm.allShards());
Runnable ni = new NodeInteraction<String>(method, args, 1, "n1", _map, 1, 3, _sm, _ne, r);
assertEquals("NodeInteraction: call testMethod on n1", ni.toString());
ni.run();
assertEquals("ClientResult: 1 results, 0 errors, 3/8 shards", r.toString());
assertEquals("n1:foo:[s2, s1, s3]", _pp.toString());
assertEquals("", _ne.toString());
}
@Test
public void testNormalCallNoShardsParam() throws Exception {
Method method = ITestServer.class.getMethod("testMethodNoShards", String.class);
Object[] args = new Object[] { "foo" };
ClientResult<String> r = new ClientResult<String>(null, _sm.allShards());
Runnable ni = new NodeInteraction<String>(method, args, -1, "n1", _map, 1, 3, _sm, _ne, r);
assertEquals("NodeInteraction: call testMethodNoShards on n1", ni.toString());
ni.run();
assertEquals("ClientResult: 1 results, 0 errors, 3/8 shards", r.toString());
assertEquals("n1:foo:null", _pp.toString());
assertEquals("", _ne.toString());
}
@Test
public void testRetries() throws Exception {
Method method = ITestServer.class.getMethod("failingMethod", String.class, String[].class);
int maxTryCount = 3;
Object[] args = new Object[] { "foo", null };
/*
* First try to call node n1 with shards s1, s2, s3. TryCount = 1. Node
* fails.
*/
ClientResult<String> r = new ClientResult<String>(null, _sm.allShards());
assertEquals(3, _map.get("n1").size());
assertTrue(_map.get("n1").contains("s1"));
assertTrue(_map.get("n1").contains("s2"));
assertTrue(_map.get("n1").contains("s3"));
Runnable ni = new NodeInteraction<String>(method, args, 1, "n1", _map, 1, maxTryCount, _sm, _ne, r);
assertEquals("NodeInteraction: call failingMethod on n1", ni.toString());
ni.run();
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards", r.toString());
assertEquals("n1:null:null", _pp.toString());
assertEquals("n3:2:{n3=[s3], n8=[s2, s1]}, n8:2:{n3=[s3], n8=[s2, s1]}", _ne.toString());
List<NodeInteractionTest.TestNodeExecutor.Call> retriesA = _ne.calls;
/*
* Now simulate running the 2 retries. TryCount = 2. Node n3 with shard s3.
* Node fails.
*/
_ne = new TestNodeExecutor();
NodeInteractionTest.TestNodeExecutor.Call call = retriesA.get(0);
assertEquals("n3", call.node);
assertEquals(1, call.nodeShardMap.get(call.node).size());
assertTrue(call.nodeShardMap.get(call.node).contains("s3"));
r = new ClientResult<String>(null, _sm.allShards());
ni = new NodeInteraction<String>(method, args, 1, call.node, call.nodeShardMap, 2, maxTryCount, _sm, _ne, r);
ni.run();
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards", r.toString());
assertEquals("n1:null:null, n3:null:null", _pp.toString());
assertEquals("n2:3:{n2=[s3]}", _ne.toString());
NodeInteractionTest.TestNodeExecutor.Call retryB1 = _ne.calls.get(0);
/*
* Second retry. TryCount = 2. Node n8 with shards s1, s2. Node fails.
*/
_ne = new TestNodeExecutor();
call = retriesA.get(1);
assertEquals("n8", call.node);
assertEquals(2, call.nodeShardMap.get(call.node).size());
assertTrue(call.nodeShardMap.get(call.node).contains("s1"));
assertTrue(call.nodeShardMap.get(call.node).contains("s2"));
r = new ClientResult<String>(null, _sm.allShards());
ni = new NodeInteraction<String>(method, args, 1, call.node, call.nodeShardMap, 2, maxTryCount, _sm, _ne, r);
ni.run();
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards", r.toString());
assertEquals("n1:null:null, n3:null:null, n8:null:null", _pp.toString());
assertEquals("n2:3:{n2=[s2], n7=[s1]}, n7:3:{n2=[s2], n7=[s1]}", _ne.toString());
List<NodeInteractionTest.TestNodeExecutor.Call> retriesB2 = _ne.calls;
/*
* Third round of retries. TryCount = 3. No further retry attempts. Node n2
* with shard s3. Node fails.
*/
_ne = new TestNodeExecutor();
assertEquals("n2", retryB1.node);
assertEquals(1, retryB1.nodeShardMap.get(retryB1.node).size());
assertTrue(retryB1.nodeShardMap.get(retryB1.node).contains("s3"));
r = new ClientResult<String>(null, _sm.allShards());
ni = new NodeInteraction<String>(method, args, 1, retryB1.node, retryB1.nodeShardMap, 3, maxTryCount, _sm, _ne, r);
ni.run();
assertEquals("ClientResult: 0 results, 1 errors, 1/8 shards", r.toString());
assertEquals("n1:null:null, n2:null:null, n3:null:null, n8:null:null", _pp.toString());
assertEquals("", _ne.toString());
/*
* Node n2 with shard s2. TryCount = 3. Node fails.
*/
_ne = new TestNodeExecutor();
call = retriesB2.get(0);
assertEquals("n2", call.node);
assertEquals(1, call.nodeShardMap.get(call.node).size());
assertTrue(call.nodeShardMap.get(call.node).contains("s2"));
r = new ClientResult<String>(null, _sm.allShards());
ni = new NodeInteraction<String>(method, args, 1, call.node, call.nodeShardMap, 3, maxTryCount, _sm, _ne, r);
ni.run();
assertEquals("ClientResult: 0 results, 1 errors, 1/8 shards", r.toString());
assertEquals("n1:null:null, n2:null:null, n3:null:null, n8:null:null", _pp.toString());
assertEquals("", _ne.toString());
/*
* Node n7 with shard s1. TryCount = 3. Node fails.
*/
_ne = new TestNodeExecutor();
call = retriesB2.get(1);
assertEquals("n7", call.node);
assertEquals(1, call.nodeShardMap.get(call.node).size());
assertTrue(call.nodeShardMap.get(call.node).contains("s1"));
r = new ClientResult<String>(null, _sm.allShards());
ni = new NodeInteraction<String>(method, args, 1, call.node, call.nodeShardMap, 3, maxTryCount, _sm, _ne, r);
ni.run();
assertEquals("ClientResult: 0 results, 1 errors, 1/8 shards", r.toString());
assertEquals("n1:null:null, n2:null:null, n3:null:null, n7:null:null, n8:null:null", _pp.toString());
assertEquals("", _ne.toString());
}
@Test
public void testMaxRetries() throws Exception {
Method method = ITestServer.class.getMethod("failingMethod", String.class, String[].class);
int maxTryCount = 2;
Object[] args = new Object[] { "foo", null };
/*
* First try to call node n1 with shards s1, s2, s3. TryCount = 1. Node
* fails.
*/
ClientResult<String> r = new ClientResult<String>(null, _sm.allShards());
assertEquals(3, _map.get("n1").size());
assertTrue(_map.get("n1").contains("s1"));
assertTrue(_map.get("n1").contains("s2"));
assertTrue(_map.get("n1").contains("s3"));
Runnable ni = new NodeInteraction<String>(method, args, 1, "n1", _map, 1, maxTryCount, _sm, _ne, r);
assertEquals("NodeInteraction: call failingMethod on n1", ni.toString());
ni.run();
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards", r.toString());
assertEquals("n1:null:null", _pp.toString());
assertEquals("n3:2:{n3=[s3], n8=[s2, s1]}, n8:2:{n3=[s3], n8=[s2, s1]}", _ne.toString());
List<NodeInteractionTest.TestNodeExecutor.Call> retriesA = _ne.calls;
/*
* Now simulate running the 2 retries. TryCount = 2. Node n3 with shard s3.
* Node fails.
*/
_ne = new TestNodeExecutor();
NodeInteractionTest.TestNodeExecutor.Call call = retriesA.get(0);
assertEquals("n3", call.node);
assertEquals(1, call.nodeShardMap.get(call.node).size());
assertTrue(call.nodeShardMap.get(call.node).contains("s3"));
r = new ClientResult<String>(null, _sm.allShards());
ni = new NodeInteraction<String>(method, args, 1, call.node, call.nodeShardMap, 2, maxTryCount, _sm, _ne, r);
ni.run();
assertEquals("ClientResult: 0 results, 1 errors, 1/8 shards", r.toString());
assertEquals("n1:null:null, n3:null:null", _pp.toString());
assertEquals("", _ne.toString());
}
@Test
public void testRetriesUserClosedResult() throws Exception {
Method method = TestServer.class.getMethod("failingMethod", String.class, String[].class);
Object[] args = new Object[] { "foo", null };
/*
* Close the result object. Then try to call node n1 with shards s1, s2, s3.
* TryCount = 1. Node fails. No retries should be attempted.
*/
ClientResult<String> r = new ClientResult<String>(null, _sm.allShards());
r.close();
assertEquals(3, _map.get("n1").size());
assertTrue(_map.get("n1").contains("s1"));
assertTrue(_map.get("n1").contains("s2"));
assertTrue(_map.get("n1").contains("s3"));
Runnable ni = new NodeInteraction<String>(method, args, 1, "n1", _map, 1, 3, _sm, _ne, r);
assertEquals("NodeInteraction: call failingMethod on n1", ni.toString());
ni.run();
assertEquals("ClientResult: 0 results, 0 errors, 0/8 shards (closed)", r.toString());
assertEquals("n1:null:null", _pp.toString());
assertEquals("", _ne.toString());
}
@Test
public void testRetriesPolicyFailure() throws Exception {
_sm.setShardMapsFail(true);
Method method = ITestServer.class.getMethod("failingMethod", String.class, String[].class);
Object[] args = new Object[] { "foo", null };
/*
* Try to call node n1 with shards s1, s2, s3. TryCount = 1. Node fails.
* When attempting to create retry node shard map, policy will throw an
* exception. Give up on retries and log error.
*/
ClientResult<String> r = new ClientResult<String>(null, _sm.allShards());
assertEquals(3, _map.get("n1").size());
assertTrue(_map.get("n1").contains("s1"));
assertTrue(_map.get("n1").contains("s2"));
assertTrue(_map.get("n1").contains("s3"));
Runnable ni = new NodeInteraction<String>(method, args, 1, "n1", _map, 1, 3, _sm, _ne, r);
assertEquals("NodeInteraction: call failingMethod on n1", ni.toString());
ni.run();
assertEquals("ClientResult: 0 results, 1 errors, 3/8 shards", r.toString());
assertEquals("net.sf.katta.client.ShardAccessException: Shard 'Test error' is currently not reachable", r
.getErrors().iterator().next().toString());
assertEquals("n1:null:null", _pp.toString());
assertEquals("", _ne.toString());
}
@Test
public void testNoProxy() throws Exception {
_pp.returnNullFor("n1");
Method method = ITestServer.class.getMethod("testMethod", String.class, String[].class);
Object[] args = new Object[] { "foo", null };
ClientResult<String> r = new ClientResult<String>(null, _sm.allShards());
Runnable ni = new NodeInteraction<String>(method, args, 1, "n1", _map, 1, 3, _sm, _ne, r);
assertEquals("NodeInteraction: call testMethod on n1", ni.toString());
ni.run();
assertEquals(2, _ne.calls.size());
// assertEquals("ClientResult: 0 results, 1 errors, 3/8 shards",
// r.toString());
assertEquals("", _pp.toString());
// assertEquals("[net.sf.katta.util.KattaException: No proxy for node: n1]",
// r.getErrors().toString());
}
@Test
public void testDefensiveArgCopy() throws Exception {
Method method = ITestServer.class.getMethod("testMethod", String.class, String[].class);
Object[] args = new Object[] { "OK", null };
ClientResult<String> r = new ClientResult<String>(null, _sm.allShards());
Runnable ni = new NodeInteraction<String>(method, args, 1, "n1", _map, 1, 3, _sm, _ne, r);
assertEquals("NodeInteraction: call testMethod on n1", ni.toString());
args[0] = "FAIL";
ni.run();
assertEquals("ClientResult: 1 results, 0 errors, 3/8 shards", r.toString());
assertEquals("n1:OK:[s2, s1, s3]", _pp.toString());
assertEquals("", _ne.toString());
}
protected static class TestNodeExecutor implements INodeExecutor {
protected class Call {
protected String node;
protected Map<String, List<String>> nodeShardMap;
protected int tryCount;
@Override
public String toString() {
return node + ":" + tryCount + ":" + nodeShardMap;
}
}
protected List<Call> calls = new ArrayList<Call>();
public void execute(String node, Map<String, List<String>> nodeShardMap, int tryCount, int maxTryCount) {
Call call = new Call();
call.node = node;
call.nodeShardMap = nodeShardMap;
call.tryCount = tryCount;
calls.add(call);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
String sep = "";
for (Call call : calls) {
sb.append(sep);
sb.append(call.toString());
sep = ", ";
}
return sb.toString();
}
}
public interface ITestServer extends VersionedProtocol {
public String testMethod(String param, String[] shards);
public String testMethodNoShards(String param);
public String failingMethod(String param, String[] shards);
}
private static class TestServer implements ITestServer, InvocationHandler {
private String _node;
private String _param;
private String[] _shards;
public TestServer(String node) {
this._node = node;
}
public String testMethod(String param, String[] shards) {
this._param = param;
this._shards = shards;
return "bar";
}
public String testMethodNoShards(String param) {
this._param = param;
this._shards = null;
return "bar";
}
public String failingMethod(String param, String[] shards) {
throw new RuntimeException("test exception");
}
@Override
public String toString() {
return _node + ":" + _param + ":" + (_shards != null ? Arrays.asList(_shards).toString() : "null");
}
public long getProtocolVersion(String arg0, long arg1) {
return 0;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if (name.equals("testMethod")) {
return testMethod((String) args[0], (String[]) args[1]);
} else if (name.equals("testMethodNoShards")) {
return testMethodNoShards((String) args[0]);
} else if (name.equals("failingMethod")) {
return failingMethod((String) args[0], (String[]) args[1]);
} else if (name.equals("toString")) {
return toString();
} else {
throw new RuntimeException("No method '" + name + "' in TestServer");
}
}
}
public static class TestProxyProvider implements WorkQueueTest.ProxyProvider {
private Map<String, VersionedProtocol> proxyCache = new HashMap<String, VersionedProtocol>();
private Map<String, TestServer> serverCache = new HashMap<String, TestServer>();
private Set<String> returnNullNodes = new HashSet<String>();
public VersionedProtocol getProxy(String node) {
if (returnNullNodes.contains(node)) {
return null;
}
VersionedProtocol vp = proxyCache.get(node);
if (vp != null) {
return vp;
}
TestServer ts = new TestServer(node);
serverCache.put(node, ts);
vp = (VersionedProtocol) Proxy.newProxyInstance(this.getClass().getClassLoader(),
new Class[] { ITestServer.class }, ts);
proxyCache.put(node, vp);
return vp;
}
public TestServer getServer(String node) {
return serverCache.get(node);
}
protected void returnNullFor(String node) {
proxyCache.remove(node);
returnNullNodes.add(node);
}
@Override
public String toString() {
List<String> nodes = new ArrayList<String>(serverCache.keySet());
Collections.sort(nodes);
StringBuilder sb = new StringBuilder();
String sep = "";
for (String node : nodes) {
sb.append(sep);
sb.append(serverCache.get(node));
sep = ", ";
}
return sb.toString();
}
}
}