/*
* JBoss, Home of Professional Open Source
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2008,
* @author JBoss Inc.
*/
package org.jboss.jbossts.qa.astests.ASCrashRecovery01;
import org.jboss.jbossts.qa.astests.taskdefs.ClientAction;
import org.jboss.jbossts.qa.astests.recovery.ASFailureSpec;
import org.jboss.jbossts.qa.astests.taskdefs.ASTestConfig;
import org.jboss.jbossts.qa.astests.taskdefs.TransactionLog;
import org.jboss.jbossts.qa.astests.crash.CrashRem;
import org.jboss.jbossts.qa.astests.crash.CrashRemHome;
import org.jboss.remoting.CannotConnectException;
import org.apache.tools.ant.BuildException;
import java.util.Map;
import java.io.*;
import javax.rmi.PortableRemoteObject;
import javax.transaction.*;
import javax.naming.NamingException;
public class Test03 implements ClientAction
{
// the longest time to wait in millis before declaring a test a failed (overridable)
private static final int MAX_TEST_TIME = 180000;
private ASTestConfig config;
private boolean isCMT = false;
private boolean clientTx = false;
private boolean isDebug = false;
private boolean expectFailure = false;
private int maxTestTime = MAX_TEST_TIME;
private String storeDir = null;
private String storeImple = "HashedActionStore";
private String storeType = null; //"StateManager/BasicAction/TwoPhaseCoordinator/AtomicAction";
private TransactionLog store;
private int existingUids;
private String name = "Test";
private String serverName = "default";
public boolean execute(ASTestConfig config, Map<String, String> params)
{
StringBuilder sb = new StringBuilder();
ASFailureSpec[] fspecs = null;
this.config = config;
for (Map.Entry<String, String> me : params.entrySet())
{
String key = me.getKey().trim();
String val = me.getValue().trim();
if ("name".equals(key))
name = val;
else if ("cmt".equals(key))
isCMT = val.equalsIgnoreCase("true");
else if ("debug".equals(key))
isDebug = val.equalsIgnoreCase("true");
else if ("serverName".equals(key))
serverName = val;
else if ("storeType".equals(key))
storeType = val;
else if ("storeDir".equals(key))
storeDir = val;
else if ("clientTx".equals(key))
clientTx = val.equalsIgnoreCase("true");
else if ("storeImple".equals(key))
storeImple = val;
else if ("testTime".equals(key))
maxTestTime = parseInt(val, "parameter testTime should represent a number of seconds: ");
else if ("specs".equals(key))
fspecs = parseSpecs(val, sb);
else if ("wait".equals(key))
suspendFor(Integer.parseInt(val));
}
sb.insert(0, ":\n").insert(0, name).insert(0, "Executing test ");
System.out.println(sb);
ClassLoader loader1 = Thread.currentThread().getContextClassLoader();
ClassLoader loader2 = this.getClass().getClassLoader();
try
{
String serverPath = config.getServerPath(serverName);
// switch class loaders since a custom ant task runs with a different loader from the loader
// that loader that loaded the class
Thread.currentThread().setContextClassLoader(loader2);
// get a handle to the transaction logs
if (storeDir == null)
storeDir = serverPath + "data/tx-object-store";
else
storeDir = serverPath + storeDir;
store = new TransactionLog(storeDir, storeImple);
if (expectFailure)
{
// this test may halt the VM so make sure the transaction log is empty
// before starting the test - then the pass/fail check is simply to
// test whether or not the log is empty (see recoverUids() below).
try
{
store.clearXids(storeType);
}
catch (Exception ignore)
{
}
existingUids = getPendingUids();
}
// run the crash test
return crashTest(fspecs);
}
catch (Exception e)
{
if (isDebug)
e.printStackTrace();
throw new BuildException(e);
}
finally
{
Thread.currentThread().setContextClassLoader(loader1);
}
}
public boolean cancel() throws UnsupportedOperationException
{
throw new UnsupportedOperationException("TODO");
}
private ASFailureSpec[] parseSpecs(String specArg, StringBuilder sb)
{
ASFailureSpec[] fspecs = config.parseSpecs(specArg);
for (ASFailureSpec spec : fspecs)
{
String name = (spec == null ? "INVALID" : spec.getName());
if (spec != null && spec.willTerminateVM())
expectFailure = true;
sb.append("\t").append(name).append('\n');
}
return fspecs;
}
private int parseInt(String intValue, String errorMessage) throws IllegalArgumentException
{
try
{
return Integer.parseInt(intValue);
}
catch (NumberFormatException e)
{
System.out.println(errorMessage + e.getMessage());
throw new IllegalArgumentException(e);
}
}
// count how many pending transaction branches there are in the transaction log
private int getPendingUids()
{
try
{
return store.getIds(storeType).size();
}
catch (Exception e)
{
e.printStackTrace();
return -1;
}
}
private CrashRem lookup(String name) throws Exception
{
Object o = config.getNamingContext(serverName).lookup(name);
CrashRemHome home = (CrashRemHome) PortableRemoteObject.narrow(o, CrashRemHome.class);
return home.create();
}
private UserTransaction startTx() throws NamingException, SystemException, NotSupportedException
{
UserTransaction tx = (UserTransaction) config.getNamingContext(serverName).lookup("UserTransaction");
tx.begin();
return tx;
}
private boolean crashTest(ASFailureSpec[] sa) throws Exception
{
UserTransaction tx = null;
try
{
CrashRem cr = lookup(isCMT ? CrashRem.CMT_JNDI_NAME : CrashRem.BMT_JNDI_NAME);
if (clientTx)
tx = startTx();
String res = cr.testXA(sa);
return "Passed".equalsIgnoreCase(res);
}
catch (CannotConnectException e)
{
if (expectFailure)
{
print("Failure was expected: " + e.getMessage());
return recoverUids();
}
else
{
System.err.println("XACrashTest:crashTest: Caught " + e);
e.printStackTrace();
}
}
catch (Throwable t)
{
t.printStackTrace();
System.err.println("XACrashTest:crashTest: Caught " + t);
}
finally {
if (clientTx)
try
{
tx.commit();
}
catch (Throwable e)
{
System.out.println("User tx commit failure: " + e.getMessage());
}
}
return false;
}
/**
* Wait for any pending transactions to recover by restaring the AS.
* @return true if all pending branches have been recovered
* @throws IOException if the server cannot be started
*/
private boolean recoverUids() throws IOException
{
int retryPeriod = 30000;
int maxWait = maxTestTime;
int pendingUids;
do
{
pendingUids = getPendingUids();
if (pendingUids == -1)
return false; // object store error
if (pendingUids <= existingUids)
return true; // all uids recovered
pendingUids -= existingUids;
print("waiting for " + pendingUids + " branches");
// wait for the server to start up the first time through
if (maxWait == maxTestTime)
config.startServer(serverName);
suspendFor(retryPeriod);
maxWait -= retryPeriod;
} while (maxWait > 0);
// the test failed to recover some uids - clear them out ready for the next test
try
{
store.clearXids(storeType);
}
catch (Exception e)
{
e.printStackTrace();
}
return false;
}
private void suspendFor(int millis)
{
try
{
Thread.sleep(millis);
}
catch (InterruptedException e)
{
System.out.println("Test " + name + " interupted");
}
}
static void print(String msg)
{
System.out.println(msg);
}
}