/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.mobicents.media.server.testsuite.general;
import jain.protocol.ip.mgcp.CreateProviderException;
import jain.protocol.ip.mgcp.JainMgcpCommandEvent;
import jain.protocol.ip.mgcp.JainMgcpEvent;
import jain.protocol.ip.mgcp.JainMgcpResponseEvent;
import jain.protocol.ip.mgcp.message.Notify;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TooManyListenersException;
import java.util.Vector;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sdp.Attribute;
import javax.sdp.SdpFactory;
import org.mobicents.media.server.testsuite.general.file.FileUtils;
import org.mobicents.media.server.testsuite.general.rtp.RtpSocketFactory;
import org.mobicents.media.server.testsuite.general.rtp.RtpSocketFactoryImpl;
import org.mobicents.media.server.testsuite.gui.ext.CallStateTableModel;
import org.mobicents.mgcp.stack.JainMgcpExtendedListener;
import org.mobicents.mgcp.stack.JainMgcpStackImpl;
import org.mobicents.mgcp.stack.JainMgcpStackProviderImpl;
/**
*
* @author baranowb
*/
public abstract class AbstractTestCase implements JainMgcpExtendedListener, Runnable, Serializable {
protected transient Logger logger = Logger.getLogger(this.getClass().getName());
private TestState testState = TestState.Stoped;
public transient final static String _CASE_FILE = "testcase.bin";
public transient final static String _COLLECTIVE_RTP_FILE = "rtp.txt";
public static final String _LINE_SEPARATOR;
static {
String lineSeparator = System.getProperty("line.separator");
_LINE_SEPARATOR = lineSeparator;
}
// Dump section
private transient File _RTP_TXT_DUMP_FILE;
private transient FileOutputStream _RTP_TXT_DUMP_FOS;
private transient OutputStreamWriter _RTP_TXT_DUMP_OSW;
// private transient FileInputStream _RTP_TXT_DUMP_FIS;
// private transient InputStreamReader _RTP_TXT_DUMP_ISR;
public static final int _TURN_OFF_BOUNDRY = -1;
// Yes, it would be good thing to ser
protected transient SdpFactory sdpFactory;
protected transient CallDisplayInterface callDisplay;
protected Map<Long, AbstractCall> callSequenceToCall;
// We mix view, but this is easier to achieve perf with that.
protected transient CallStateTableModel model;
// protected part - some variables that we might use.
protected InetAddress clientTestNodeAddress;
protected InetAddress serverJbossBindAddress;
// timestamp :), its used for files
protected long testTimesTamp = System.currentTimeMillis();
protected transient File testDumpDirectory;
protected transient ScheduledFuture callCreatorTask;
protected transient ScheduledFuture gracefulStopTask;
// Timer guard:
protected transient final ScheduledExecutorService timeGuard;
//
protected transient final ScheduledExecutorService executors;
// Some getters
// Some stats
protected long ongoingCallNumber;
protected long errorCallNumber;
protected long completedCallNumber;
protected long totalCalls;
protected long maxErrorCallNumber;
protected transient RtpSocketFactory socketFactory;
// FIXME: this will go into MGCP test case, will be removed from here
// some mgcp magic
protected transient JainMgcpStackImpl stack;
protected transient JainMgcpStackProviderImpl provider;
// We need this to map TXID to Call :)
protected transient Map<Integer, AbstractCall> mgcpTransactionToProxy = new HashMap<Integer, AbstractCall>();
protected transient Map<String, AbstractCall> requestIdIdToProxy = new HashMap<String, AbstractCall>();
public AbstractTestCase() {
this.callSequenceToCall = new HashMap<Long, AbstractCall>();
// model = new CallStateTableModel(this.callSequenceToCall);
AbstractCall.resetSequence();
NamedThreadFactory executorsThreadFactory = new NamedThreadFactory("ExecutorsTestCaseFactory");
NamedThreadFactory timeGuardThreadFactory = new NamedThreadFactory("GuardThreadFactoryTestCaseFactory");
executors = Executors.newScheduledThreadPool(2, executorsThreadFactory);
timeGuard = Executors.newScheduledThreadPool(1, timeGuardThreadFactory);
}
private void init() throws SocketException, IOException {
// init streams
boolean finished = false;
try {
_RTP_TXT_DUMP_FOS = new FileOutputStream(_RTP_TXT_DUMP_FILE);
_RTP_TXT_DUMP_OSW = new OutputStreamWriter(_RTP_TXT_DUMP_FOS);
// _RTP_TXT_DUMP_FIS = new FileInputStream(_RTP_TXT_DUMP_FILE);
// _RTP_TXT_DUMP_ISR = new InputStreamReader(_RTP_TXT_DUMP_FIS);
finished = true;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (!finished) {
if (_RTP_TXT_DUMP_OSW != null) {
_RTP_TXT_DUMP_OSW.close();
_RTP_TXT_DUMP_OSW = null;
_RTP_TXT_DUMP_FOS = null;
}
}
}
if (this.socketFactory == null) {
this.socketFactory = new RtpSocketFactoryImpl();
// this.socketFactory.setPortRange("5000-10000");
}
if (this.socketFactory != null) {
this.socketFactory.setBindAddress(this.callDisplay.getLocalAddress());
this.socketFactory.setFormatMap(this.callDisplay.getCodecs());
this.socketFactory.start();
}
}
public OutputStreamWriter getRtpOSW() {
return _RTP_TXT_DUMP_OSW;
}
public InputStreamReader getRtpISR() {
FileInputStream _RTP_TXT_DUMP_FIS;
try {
_RTP_TXT_DUMP_FIS = new FileInputStream(_RTP_TXT_DUMP_FILE);
InputStreamReader _RTP_TXT_DUMP_ISR = new InputStreamReader(_RTP_TXT_DUMP_FIS);
return _RTP_TXT_DUMP_ISR;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public ScheduledExecutorService getExecutors() {
return executors;
}
protected void incrementOngoignCall() {
this.ongoingCallNumber++;
this.totalCalls++;
}
protected void decrementOngoingCall() {
this.ongoingCallNumber--;
}
protected void incrementErrorCall() {
this.errorCallNumber++;
}
protected void incrementCompletedCall() {
this.completedCallNumber++;
}
public long getTestTimeStamp() {
return this.testTimesTamp;
}
public InetAddress getClientTestNodeAddress() {
return this.clientTestNodeAddress;
}
public InetAddress getServerJbossBindAddress() {
return this.serverJbossBindAddress;
}
public CallDisplayInterface getCallDisplayInterface() {
return this.callDisplay;
}
public AbstractCall getCallBySequence(Long seq) {
AbstractCall ac = this.callSequenceToCall.get(seq);
if (ac != null)
ac.setTestCase(this);
return ac;
}
public void callStateChanged(AbstractCall c) {
CallState callState = c.getState();
if (callState == CallState.INITIAL) {
this.incrementOngoignCall();
} else if (callState == CallState.ENDED) {
this.decrementOngoingCall();
this.incrementCompletedCall();
// as soon as we end one call, we should try to start another.
this.checkForCallInit();
} else if (callState == CallState.IN_ERROR) {
this.decrementOngoingCall();
this.incrementErrorCall();
// as soon as we end one call, we should try to start another.
this.checkForCallInit();
}
// System.err.println("updateCallView:"+this.ongoingCallNumber);
this.callDisplay.updateCallView();
// this is forterm;
if (this.testState == TestState.Terminating) {
if (getOngoingCallNumber() == 0) {
this.stop(false);
}
}
}
public CallStateTableModel getTableModel() {
return this.model;
}
public long getCompletedCallNumber() {
return this.completedCallNumber;
}
public long getErrorCallNumber() {
return this.errorCallNumber;
}
public long getOngoingCallNumber() {
return this.ongoingCallNumber;
}
public void setMaxErrorCallNumber(long v) {
this.maxErrorCallNumber = v;
}
public long getTotalCallNumber() {
return this.totalCalls;
}
public void stop(boolean onGracefull) {
synchronized (this.testState) {
switch (this.testState) {
case Terminating:
if (!onGracefull) {
return;
}
try {
if (this.provider != null) {
try {
this.provider.removeJainMgcpListener(this);
this.stack.deleteProvider(this.provider);
} catch (Exception e) {
e.printStackTrace();
}
}
if (this.stack != null) {
try {
this.stack.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (callCreatorTask != null) {
callCreatorTask.cancel(true);
}
// FIXME: add more?
try {
for (AbstractCall call : this.callSequenceToCall.values()) {
if (call.getState() == CallState.ESTABILISHED || call.getState() == CallState.INITIAL) {
call.stop();
}
}
} catch (Exception e) {
e.printStackTrace();
}
if (_RTP_TXT_DUMP_OSW != null) {
try {
_RTP_TXT_DUMP_OSW.flush();
_RTP_TXT_DUMP_OSW.close();
_RTP_TXT_DUMP_OSW = null;
_RTP_TXT_DUMP_FOS = null;
} catch (Exception e) {
e.printStackTrace();
}
}
if (this.socketFactory != null)
try {
this.socketFactory.stop();
} catch (Exception e) {
e.printStackTrace();
}
// Now lets serialize.
serialize();
// dumpSampleTraffic();
} finally {
this.testState = TestState.Stoped;
this.gracefulStopTask = null;
if (this.timeGuard != null) {
this.timeGuard.shutdownNow();
}
if (this.executors != null) {
this.executors.shutdownNow();
}
}
this.testState = TestState.Stoped;
break;
case Running:
this.testState = TestState.Terminating;
// this.gracefulStopTask = this.
// so we dont have to press stop twice, this is stupid.
if (this.gracefulStopTask == null) {
this.gracefulStopTask = this.executors.schedule(new GracefulStopTask(this), this.callDisplay.getCallDuration() + 1000, TimeUnit.MILLISECONDS);
}
break;
default:
break;
}
}
}
public void start() throws CreateProviderException, TooManyListenersException {
try {
stop(false);
this.clientTestNodeAddress = InetAddress.getByName(this.callDisplay.getLocalAddress());
this.serverJbossBindAddress = InetAddress.getByName(this.callDisplay.getRemoteAddress());
this.stack = new JainMgcpStackImpl(this.clientTestNodeAddress, this.callDisplay.getLocalPort());
this.provider = (JainMgcpStackProviderImpl) this.stack.createProvider();
this.provider.addJainMgcpListener(this);
testState = TestState.Running;
onCPSChange();
} catch (UnknownHostException ex) {
Logger.getLogger(AbstractTestCase.class.getName()).log(Level.SEVERE, null, ex);
}
}
public TestState getTestState() {
return this.testState;
}
public RtpSocketFactory getSocketFactory() {
return socketFactory;
}
public void setCallDisplay(CallDisplayInterface cdi) throws IllegalStateException, SocketException, IOException {
this.callDisplay = cdi;
this.clientTestNodeAddress = InetAddress.getByName(this.callDisplay.getRemoteAddress());
this.serverJbossBindAddress = InetAddress.getByName(this.callDisplay.getRemoteAddress());
this.sdpFactory = SdpFactory.getInstance();
this.testDumpDirectory = new File(cdi.getDefaultDataDumpDirectory(), "" + this.testTimesTamp);
if (!this.testDumpDirectory.exists()) {
if (!this.testDumpDirectory.mkdirs()) {
throw new IllegalStateException("Failed to create dirs: " + this.testDumpDirectory);
}
} else {
// This shoudl not happen, but just in case.
if (this.testDumpDirectory.isDirectory() && this.testDumpDirectory.canWrite()) {
} else {
throw new IllegalStateException("Failed to validate dump dir, its either not writeable or is not a directory: " + this.testDumpDirectory);
}
}
_RTP_TXT_DUMP_FILE = new File(this.testDumpDirectory, _COLLECTIVE_RTP_FILE);
this.init();
}
// This method is used on loaded test case
public void setCallDisplay(CallDisplayInterface cdi, File testDumpDirectory) throws UnknownHostException, IllegalStateException {
this.callDisplay = cdi;
this.sdpFactory = SdpFactory.getInstance();
// this.testDumpDirectory = new
// File(testDumpDirectory,""+this.testTimesTamp);
this.testDumpDirectory = testDumpDirectory;
_RTP_TXT_DUMP_FILE = new File(this.testDumpDirectory, _COLLECTIVE_RTP_FILE);
model = new CallStateTableModel(this.callSequenceToCall);
for (AbstractCall call : this.callSequenceToCall.values()) {
call.setDumpDir(testDumpDirectory);
}
}
public void onCPSChange() {
if (testState == TestState.Stoped) {
return;
}
// we changed CPS.
if (this.callCreatorTask != null) {
this.callCreatorTask.cancel(true);
}
int cps = this.getCallDisplayInterface().getCPS();
if (cps == 0) {
return;
}
int delta = 1000 / this.getCallDisplayInterface().getCPS();
// we use delta,delta, cause we dont want sudden rush in CPS
this.callCreatorTask = this.executors.scheduleAtFixedRate(this, delta, delta, TimeUnit.MILLISECONDS);
// this.run();
}
public void onCallLengthChange() {
}
public abstract AbstractCall getNewCall();
Vector<Attribute> getSDPAttributes() {
return this.callDisplay.getCodecs();
}
public SdpFactory getSdpFactory() {
return this.sdpFactory;
}
public File getTestDumpDirectory() {
return this.testDumpDirectory;
}
// run in which we create more calls :)
public void run() {
// For some twisted reason constructo does not work...
// model.setCallData(this.callSequenceToCall);
if (this.testState == TestState.Running) {
if (this.maxErrorCallNumber != _TURN_OFF_BOUNDRY && this.errorCallNumber >= this.maxErrorCallNumber) {
}
if (this.callDisplay.getMaxConcurrentCalls() != _TURN_OFF_BOUNDRY && this.ongoingCallNumber >= this.callDisplay.getMaxConcurrentCalls()) {
return;
}
if (this.callDisplay.getMaxCalls() != _TURN_OFF_BOUNDRY && this.totalCalls == this.callDisplay.getMaxCalls()) {
this.stop(false);
return;
}
try {
// This creates call, which knows how to estabilish itself and
// how long it should linger on as active.
AbstractCall c = this.getNewCall();
this.callSequenceToCall.put(c.getSequence(), c);
callStateChanged(c);
c.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void checkForCallInit() {
if (this.callDisplay.getMaxConcurrentCalls() != -1 && this.callDisplay.getCPS() > 0) {
try {
run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// some handy methods
public JainMgcpStackProviderImpl getProvider() {
return this.provider;
}
// Event handlers
public void processMgcpCommandEvent(JainMgcpCommandEvent command) {
// For now we dont care for reqeust sent from MMS
if (command instanceof Notify) {
Notify notify = (Notify) command;
AbstractCall cp = getCall(notify.getRequestIdentifier().toString());
if (cp != null) {
cp.processMgcpCommandEvent(command);
}
}
}
public void processMgcpResponseEvent(JainMgcpResponseEvent response) {
// System.out.println("Recived response "+ response);
try {
AbstractCall cp = getCall(response);
if (cp != null) {
cp.processMgcpResponseEvent(response);
} else {
System.err.println("NO CALL");
}
} catch (RuntimeException re) {
re.printStackTrace();
}
}
public void transactionEnded(int arg0) {
AbstractCall cp = getCall(arg0);
if (cp != null) {
cp.transactionEnded(arg0);
} else {
logger.severe("No call proxy for txid: " + arg0);
}
}
public void transactionRxTimedOut(JainMgcpCommandEvent commandTimedOut) {
AbstractCall cp = getCall(commandTimedOut);
if (cp != null) {
cp.transactionRxTimedOut(commandTimedOut);
} else {
logger.severe("No call proxy for txid: " + commandTimedOut.getTransactionHandle() + " for timed out event");
}
}
public void transactionTxTimedOut(JainMgcpCommandEvent commandTimeOut) {
AbstractCall cp = getCall(commandTimeOut);
if (cp != null) {
cp.transactionTxTimedOut(commandTimeOut);
} else {
logger.severe("No call proxy for txid: " + commandTimeOut.getTransactionHandle() + " for timed out event2");
}
}
// CALL MGMT
protected AbstractCall getCall(JainMgcpEvent mgcpEvent) {
return this.mgcpTransactionToProxy.get(mgcpEvent.getTransactionHandle());
}
protected AbstractCall getCall(int txID) {
return this.mgcpTransactionToProxy.get(txID);
}
public void removeCall(JainMgcpEvent mgcpEvent) {
this.removeCall(mgcpEvent.getTransactionHandle());
}
public void removeCall(int txID) {
this.mgcpTransactionToProxy.remove(txID);
}
public void addCall(String ri, AbstractCall cp) {
this.requestIdIdToProxy.put(ri, cp);
}
public void removeCall(String ri) {
this.requestIdIdToProxy.remove(ri);
}
public AbstractCall getCall(String ri) {
return this.requestIdIdToProxy.get(ri);
}
public void addCall(JainMgcpEvent mgcpEvent, AbstractCall cp) {
this.mgcpTransactionToProxy.put(mgcpEvent.getTransactionHandle(), cp);
}
/**
* Custom deserialization is needed.
*/
private void readObject(ObjectInputStream aStream) throws IOException, ClassNotFoundException {
aStream.defaultReadObject();
}
/**
* Perofrms all serialization actions
*/
protected void serialize() {
this.localAddress = this.callDisplay.getLocalAddress();
this.localPort = this.callDisplay.getLocalPort();
this.remoteAddress = this.callDisplay.getRemoteAddress();
this.remotePort = this.callDisplay.getRemotePort();
this.cps = this.callDisplay.getCPS();
this.callDuration = this.callDisplay.getCallDuration();
this.maxCalls = this.callDisplay.getMaxCalls();
this.maxConcurrentCalls = this.callDisplay.getMaxConcurrentCalls();
this.maxFailCalls = this.callDisplay.getMaxFailCalls();
FileUtils.serializeTestCase(this);
}
/**
* Custom serialization is needed.
*/
private void writeObject(ObjectOutputStream aStream) throws IOException {
aStream.defaultWriteObject();
}
/**
* This method is called after stop, to dump case data.
*/
private class GracefulStopTask implements Runnable {
private AbstractTestCase atc;
public GracefulStopTask(AbstractTestCase atc) {
super();
this.atc = atc;
}
/*
* (non-Javadoc)
*
* @see java.lang.Runnable#run()
*/
public void run() {
atc.stop(true);
}
}
/**
* @return
*/
public String getExampleData() {
StringBuffer sb = new StringBuffer();
if (this.callSequenceToCall != null && this.callSequenceToCall.size() > 0) {
int index0 = this.callSequenceToCall.size() / 2;
Iterator<Long> seqIterator = this.callSequenceToCall.keySet().iterator();
while (index0 > 0) {
// seqIterator.next();
index0--;
}
AbstractCall call = null;
while (call == null && seqIterator.hasNext()) {
Long seq = seqIterator.next();
call = this.callSequenceToCall.get(seq);
if (call.getState() == CallState.IN_ERROR || call.getState() == CallState.TIMED_OUT) {
call = null;
continue;
} else {
break;
}
}
if (call != null) {
sb.append((this.callDuration + AbstractTestCase._LINE_SEPARATOR));
sb.append((this.cps + AbstractTestCase._LINE_SEPARATOR));
sb.append((this.maxConcurrentCalls + AbstractTestCase._LINE_SEPARATOR));
sb.append((this.maxFailCalls + AbstractTestCase._LINE_SEPARATOR));
sb.append((this.getErrorCallNumber() + AbstractTestCase._LINE_SEPARATOR));
sb.append((this.maxCalls + AbstractTestCase._LINE_SEPARATOR));
sb.append((this.getTotalCallNumber() + AbstractTestCase._LINE_SEPARATOR));
call.setTestCase(this);
// sb.append((call.getCallID().toString()+AbstractTestCase._LINE_SEPARATOR));
sb.append((call.getSequence() + AbstractTestCase._LINE_SEPARATOR));
sb.append((call.getAvgJitter() + AbstractTestCase._LINE_SEPARATOR));
sb.append((call.getPeakJitter() + AbstractTestCase._LINE_SEPARATOR));
//
sb.append(call.getCallSpecificData());
} else {
logger.severe("Failed to find call to add data to collective dump file.");
}
}
if (sb.length() > 0) {
return sb.toString();
} else {
return null;
}
}
// Those are only for dump purposes
private String localAddress = "127.0.0.1", remoteAddress = "127.0.0.1";
private int localPort = 2428, remotePort = 2427;
private int cps = 1;
private long callDuration = 2500;
private long maxCalls = AbstractTestCase._TURN_OFF_BOUNDRY;
private int maxConcurrentCalls = AbstractTestCase._TURN_OFF_BOUNDRY;
private int maxFailCalls = AbstractTestCase._TURN_OFF_BOUNDRY;
}