package com.yahoo.dtf.comm.rpc;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import com.yahoo.dtf.comm.rpc.ActionResult;
import com.yahoo.dtf.comm.rpc.NodeInterface;
import com.yahoo.dtf.DTFConstants;
import com.yahoo.dtf.DTFNode;
import com.yahoo.dtf.NodeInfo;
import com.yahoo.dtf.NodeState;
import com.yahoo.dtf.actions.Action;
import com.yahoo.dtf.actions.component.Component;
import com.yahoo.dtf.actions.flowcontrol.Sequence;
import com.yahoo.dtf.actions.protocol.CleanUpHook;
import com.yahoo.dtf.actions.protocol.Connect;
import com.yahoo.dtf.actions.protocol.Lock;
import com.yahoo.dtf.actions.protocol.Rename;
import com.yahoo.dtf.comm.Comm;
import com.yahoo.dtf.components.CleanUpState;
import com.yahoo.dtf.components.ComponentReturnHook;
import com.yahoo.dtf.config.Config;
import com.yahoo.dtf.exception.DTFException;
import com.yahoo.dtf.logger.DTFLogger;
import com.yahoo.dtf.recorder.internal.RemoteRecorder;
import com.yahoo.dtf.state.ActionState;
import com.yahoo.dtf.state.DTFState;
public class Node implements NodeInterface,CleanUpHook {
public static final String BASE_CONFIG = "BaseConfig";
public static final String ACTION_RESULT_CONTEXT = "dtf.action.result.ctx";
public static final String ACTION_DTFX_THREADID = "dtf.dtfx.threadid.ctx";
private static ArrayList<String> ctx = new ArrayList<String>();
public Boolean heartbeat() {
Comm.heartbeat();
return Boolean.TRUE;
}
private static AtomicLong executions = new AtomicLong(0);
public ActionResult execute(String id, Action action) throws RemoteException {
ActionResult result = new ActionResult();
ActionState as = ActionState.getInstance();
executions.incrementAndGet();
String threadId = null;
if (action instanceof Sequence)
threadId = ((Sequence)action).getThreadid();
try {
if (DTFNode.getType().equals("dtfc")) {
if (id.equals("dtfc")) {
DTFState state = as.getState(threadId).duplicate();
state.resetContext();
as.setState(state);
state.setConfig(as.getState(threadId).getConfig());
state.registerContext(ACTION_DTFX_THREADID, threadId);
state.registerContext(ACTION_RESULT_CONTEXT, result);
try {
action.execute();
} finally {
state.unRegisterContext(ACTION_DTFX_THREADID);
state.unRegisterContext(ACTION_RESULT_CONTEXT);
as.delState();
}
} else {
NodeInfo node = NodeState.getInstance().getNodeInfo(id);
return node.getClient().sendAction(id, action);
}
} else {
if (DTFNode.getType().equals(DTFConstants.DTFX_ID) &&
threadId != null) {
/*
* XXX: I don't like this solution because it is in no way
* elegant or easy to understand.
*
* Remote events being executed on the dtfx under a specified
* threadid
*
* We need to dup the state and also make sure that the Global
* Context isn't shared by other threads calling back, but make sure
* to have the same properties.
*
*/
DTFState state = as.getState(threadId).duplicate();
state.resetContext();
as.setState(state);
// by default clone also clones the Config but in this case we
// want callbacks to affect the currently running Config for that
// specific threadid
state.setConfig(as.getState(threadId).getConfig());
state.registerContext(ACTION_DTFX_THREADID, threadId);
state.registerContext(ACTION_RESULT_CONTEXT, result);
try {
action.execute();
} finally {
state.unRegisterContext(ACTION_DTFX_THREADID);
state.unRegisterContext(ACTION_RESULT_CONTEXT);
as.delState();
}
} else {
/*
* Use the base config to create the state for all of the
* remote calls to this component. The base config is
* recreated when this component is locked and is a way of
* isolating executions between different test runs.
*/
DTFState state = null;
/*
* This avoids conflicting state id name with an existing
* thread on this side.
*/
String rkey = CleanUpState.genThreadName(threadId);
if ( threadId != null && as.hasState(rkey) )
state = as.getState(rkey);
// first call to this agent by this remote thread
if ( state == null ) {
DTFState main = as.getState(BASE_CONFIG);
Config config = (Config) main.getConfig().clone();
state = new DTFState(config, main.getStorage());
state.setGlobalContext(main.getGlobalContext());
state.setComm(main.getComm());
state.setFunctions(main.getFunctions());
state.setReferences(main.getReferences());
state.setRecorder(main.getRecorder());
if ( threadId != null ) {
as.setState(rkey, state);
synchronized(ctx) { ctx.add(rkey); }
}
}
as.setState(state);
if ( action instanceof Lock ) {
state.registerContext(ACTION_RESULT_CONTEXT, result);
try {
action.execute();
} finally {
state.unRegisterContext(ACTION_RESULT_CONTEXT);
}
return result;
}
RemoteRecorder rrecorder = null;
// create the remote recorder only after setting the state
// so that this recorder can now save state related changes
// correctly
if ( threadId != null ) {
rrecorder = new RemoteRecorder(result, true, threadId);
state.registerContext(ACTION_DTFX_THREADID, threadId);
} else {
//Action.getLogger().info("threadid shouldn't be null " + action);
}
state.registerContext(ACTION_RESULT_CONTEXT, result);
try {
if ( rrecorder != null )
Action.pushRecorder(rrecorder, null);
action.execute();
String owner = null;
Lock l = DTFNode.getOwner();
if (l != null) owner = l.getOwner();
boolean nohooks = Boolean.valueOf("" +
state.getContext("noreturnhooks"));
if ( owner != null && !nohooks ) {
// call ComponentReturnHooks
ArrayList<ComponentReturnHook> rhooks =
Component.getComponentReturnHooks();
for (int i = 0; i < rhooks.size(); i++) {
ComponentReturnHook crh = rhooks.get(i);
//result.addActions(crh.returnToRunner(owner));
Comm comm = Action.getComm();
List<Action> results = crh.returnToRunner(owner);
Sequence sequence = new Sequence();
sequence.setThreadID(threadId);
sequence.addActions(results);
comm.sendAction(owner, sequence);
// Action.getLogger().info("about to send [" +
// threadId + "]");
// for (Action a : results) {
// comm.sendActionToCaller(owner, a);
// }
}
}
} finally {
state.unRegisterContext(ACTION_DTFX_THREADID);
state.unRegisterContext(ACTION_RESULT_CONTEXT);
state.unRegisterContext("noreturnhooks");
if ( threadId != null ) {
Action.popRecorder();
if ( as.hasState(threadId) )
as.delState();
}
}
}
}
return result;
} catch (Throwable t) {
/*
* This code exists because we need to make sure to return a message
* that can easily be used by the person developing or using a tag
* to identify what the problem was. Since sometimes a new tag can
* throw Exceptions that are not fully serializable we need to
* handle this gracefully and be able to at least report that the
* exception message logged on the agent for review.
*
* When this situation is hit this is in fact an issue with the tag
* that was created throwing bad exceptions. So if you hit this
* point in the code you really shouldn't be throwing exceptions
* that are not serializable, because that breaks quite a few things
* in the way remote actions are handled.
*
*/
if ( isSerializable(t) ) {
throw new RemoteException("Error executing action.",t);
} else {
DTFLogger log = Action.getLogger();
log.error("Non serializable exception caught, you should fix" +
" this by throwing only exceptions that are " +
" serializable so DTF can report remote exceptions " +
" correctly on the runner side.",t);
DTFException e =
new DTFException("Non serializable exception on agent side [" +
t.getMessage() +
"], check agent for more details.");
throw new RemoteException("Error executing action.",e);
}
} finally {
executions.decrementAndGet();
}
}
public static boolean isExecuting() { return executions.intValue() != 0; }
public void cleanup() throws DTFException {
ActionState as = ActionState.getInstance();
for (int i = 0; i < ctx.size(); i++) {
as.delState(ctx.remove(i));
}
if ( Action.getLogger().isDebugEnabled() )
Action.getLogger().debug("Cleaned up " + ctx.size() + " states.");
}
public static void cleanedup(String id) {
synchronized (ctx) {
ctx.remove(id);
}
}
public boolean isSerializable(Object obj) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return true;
} catch (IOException e) {
return false;
}
}
public ActionResult register(Connect connect) throws RemoteException {
try {
ActionResult result = new ActionResult();
connect.execute();
Rename rename = new Rename();
rename.setName(connect.getId());
result.addAction(rename);
return result;
} catch (DTFException e) {
throw new RemoteException("Error registering.",e);
}
}
public ActionResult unregister(Connect connect) throws RemoteException {
try {
ActionResult result = new ActionResult();
NodeState ns = NodeState.getInstance();
ns.removeNode(connect);
return result;
} catch (DTFException e) {
throw new RemoteException("Error unregistering.",e);
}
}
}