blob: ff548b38e8fdaaf3a072e3a798082c0a55d038a6 [file]
/*
*
* Derby - Class org.apache.derbyTesting.junit.NetworkServerTestSetup
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.derbyTesting.junit;
import java.io.FileNotFoundException;
import java.net.InetAddress;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import org.apache.derby.drda.NetworkServerControl;
import org.apache.derby.shared.common.error.ExceptionUtil;
import org.apache.derby.shared.common.info.JVMInfo;
/**
* Test decorator that starts the network server on startup
* and stops it on teardown.
*
*/
final public class NetworkServerTestSetup extends BaseTestSetup {
/**
* <p>
* Setting maximum wait time to 4 minutes by default. On some platforms
* it may take this long to start the server. See for example
* <a href="http://bugs.sun.com/view_bug.do?bug_id=6483406">this JVM
* bug</a> that sometimes makes server startup take more than 3 minutes.
* </p>
*
* <p>
* Increasing the wait time should not adversely affect those
* systems with fast port turnaround as the actual code loops for
* SLEEP_TIME intervals, so should never see WAIT_TIME.
* For even slower systems (or for faster systems) the default value can
* be overwritten using the property derby.tests.networkServerStartTimeout
* (which is in seconds, rather than milliseconds)
* </p>
*/
private static final long DEFAULT_WAIT_TIME = 240000;
private static final long WAIT_TIME = getWaitTime();
/** Sleep for 100 ms before pinging the network server (again) */
private static final int SLEEP_TIME = 100;
public static final String HOST_OPTION = "-h";
private static long waitTime = WAIT_TIME;
private FileOutputStream serverOutput;
private final boolean asCommand;
private final boolean startServerAtSetup;
private final boolean useSeparateProcess;
private final boolean serverShouldComeUp;
/**
* System properties to set on the command line (using -D)
* only when starting the server in a separate virtual machine.
*/
private final String[] systemProperties;
/**
* Startup arguments for the command line
* only when starting the server in a separate virtual machine.
*/
private String[] startupArgs;
/**
* The server as a process if started in a different vm.
*/
private SpawnedProcess spawnedServer;
/**
* Decorates a test with the NetworkServerTestSetup.
*
* Runs the server using the current configuration (at the time
* of setup).
*
* @param asCommand True to start using NetworkServerControl.main()
* within the same virtual machine, false to use NetworkServerControl.start
* (also within the same JVM).
*
* @see NetworkServerControl#main(String[])
* @see NetworkServerControl#start(PrintWriter)
*/
public NetworkServerTestSetup(Test test, boolean asCommand) {
super(test);
this.asCommand = asCommand;
this.systemProperties = null;
this.startupArgs = null;
this.useSeparateProcess = false;
this.serverShouldComeUp = true;
this.startServerAtSetup = true;
}
/**
* Decorates a test with the NetworkServerTestSetup.
*
* Sets up the server using the current configuration. Whether or not the
* server is actually started at setup time is determined by the value of
* the passed parameters.
*
* @param test the Test for which this setup is used
* @param asCommand True to start using NetworkServerControl.main()
* within the same virtual machine, false to use NetworkServerControl.start()
* (also within the same virtual machine).
* @param startServerAtSetup True to start the Network Server at setup time,
* False otherwise.
*
* @see NetworkServerControl#main(String[])
* @see NetworkServerControl#start(PrintWriter)
*/
public NetworkServerTestSetup( Test test,
boolean asCommand,
boolean startServerAtSetup) {
super(test);
this.asCommand = asCommand;
this.systemProperties = null;
this.startupArgs = null;
this.useSeparateProcess = false;
this.serverShouldComeUp = true;
this.startServerAtSetup = startServerAtSetup;
}
/**
* Decorator for starting up with specific command args
* and system properties. Server is always started up
* in a separate process with a separate virtual machine.
* <P>
* If the classes are being loaded from the classes
* folder instead of jar files then this will start
* the server up with no security manager using -noSecurityManager,
* unless the systemProperties or startupArgs set up any security
* manager.
* This is because the default policy
* installed by the network server only works from jar files.
* If this not desired then the test should skip the
* fixtures when loading from classes or
* install its own security manager.
*/
public NetworkServerTestSetup
(
Test test,
String[] systemProperties,
String[] startupArgs,
boolean serverShouldComeUp
)
{
super(test);
this.asCommand = true;
this.systemProperties = systemProperties;
this.startupArgs = startupArgs;
this.useSeparateProcess = true;
this.serverShouldComeUp = serverShouldComeUp;
this.startServerAtSetup = true;
}
/**
* Start the network server.
*/
protected void setUp() throws Exception {
BaseTestCase.println("Starting network server:");
networkServerController = getNetworkServerControl();
if (startServerAtSetup)
{
// DERBY-4201: A network server instance used in an earlier test
// case might not have completely shut down and released the server
// port yet. Wait here until the port has been released.
waitForAvailablePort();
if (useSeparateProcess)
{ spawnedServer = startSeparateProcess(); }
else if (asCommand)
{ startWithCommand(); }
else
{ startWithAPI(); }
if (serverShouldComeUp)
{
if (!pingForServerStart(networkServerController)) {
String msg = getTimeoutErrorMsg("network server to start");
// Dump the output from the spawned process
// and destroy it.
if (spawnedServer != null) {
spawnedServer.complete(2000);
msg = spawnedServer.getFailMessage(msg);
spawnedServer = null;
}
//DERBY-6012 print thread dump and java core
JVMInfo.javaDump();
fail(msg + Utilities.NL + ExceptionUtil.dumpThreads());
}
}
}
}
/**
* Wait until the server port has been released by server instances used
* by earlier test cases, or until the timeout specified by
* {@link #getWaitTime()} has elapsed.
*
* @throws AssertionFailedError if the port didn't become available before
* the timeout
* @throws InterruptedException if the thread was interrupted while waiting
* for the port to become available
* @throws UnknownHostException if the host name couldn't be resolved
*/
public static void waitForAvailablePort()
throws InterruptedException, UnknownHostException {
waitForAvailablePort(TestConfiguration.getCurrent().getPort());
}
/**
* Wait until the specified port has been released by
* by earlier test cases, or until the timeout specified by
* {@link #getWaitTime()} has elapsed.
*
* @param port value.
* @throws AssertionFailedError if the port didn't become available before
* the timeout
* @throws InterruptedException if the thread was interrupted while waiting
* for the port to become available
* @throws UnknownHostException if the host name couldn't be resolved
*/
public static void waitForAvailablePort(int port)
throws InterruptedException, UnknownHostException {
InetAddress serverAddress = InetAddress.getByName(
TestConfiguration.getCurrent().getHostName());
long giveUp = System.currentTimeMillis() + getWaitTime();
BaseTestCase.println(
"probing port for availability: " + serverAddress + ":" + port);
while (true) {
try {
probeServerPort(port, serverAddress);
break;
} catch (IOException ioe) {
if (System.currentTimeMillis() < giveUp) {
Thread.sleep(SLEEP_TIME);
} else {
BaseTestCase.fail(
getTimeoutErrorMsg("server port to become available",
port),
ioe);
}
}
}
}
/**
* Check if a server socket can be opened on the specified port.
*
* @param port the port to check
* @param addr the address of the network interface
* @throws IOException if a server socket couldn't be opened
*/
private static void probeServerPort(final int port, final InetAddress addr)
throws IOException {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() throws IOException {
new ServerSocket(port, 0, addr).close();
return null;
}
});
} catch (PrivilegedActionException pae) {
throw (IOException) pae.getCause();
}
}
private void startWithAPI() throws Exception
{
serverOutput = AccessController.doPrivileged(
new PrivilegedAction<FileOutputStream>() {
public FileOutputStream run() {
File logs = new File("logs");
logs.mkdir();
File console = new File(logs, "serverConsoleOutput.log");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(console.getPath(), true);
} catch (FileNotFoundException ex) {
ex.printStackTrace();
}
return fos;
}
});
networkServerController.start(new PrintWriter(serverOutput));
}
private void startWithCommand() throws Exception
{
// start the server through the command line
// arguments using a new thread to do so.
new Thread(
new Runnable() {
public void run() {
String[] args = getDefaultStartupArgs( false );
org.apache.derby.drda.NetworkServerControl.main( args );
}
}, "NetworkServerTestSetup command").start();
}
private SpawnedProcess startSeparateProcess() throws Exception
{
ArrayList<String> al = new ArrayList<String>();
boolean skipHostName = false;
// Loading from classes need to work-around the limitation of the
// default policy file doesn't work with classes. Similarly, if we are
// running with Emma we don't run with the security manager, as the
// default server policy doesn't contain needed permissions and,
// additionally, Emma sources do not use doPrivileged blocks anyway.
if (!TestConfiguration.loadingFromJars() ||
BaseTestCase.runsWithEmma() || BaseTestCase.runsWithJaCoCo())
{
boolean setNoSecurityManager = true;
for (int i = 0; i < systemProperties.length; i++)
{
if (systemProperties[i].startsWith("java.security."))
{
setNoSecurityManager = false;
break;
}
}
for (int i = 0; i < startupArgs.length; i++)
{
if (startupArgs[i].equals("-noSecurityManager"))
{
setNoSecurityManager = false;
break;
}
}
if (setNoSecurityManager)
{
String[] newArgs = new String[startupArgs.length + 1];
System.arraycopy(startupArgs, 0, newArgs, 0, startupArgs.length);
newArgs[newArgs.length - 1] = "-noSecurityManager";
startupArgs = newArgs;
}
}
int count = systemProperties.length;
for ( int i = 0; i < count; i++ )
{
al.add( "-D" + systemProperties[ i ] );
}
al.add( "org.apache.derby.drda.NetworkServerControl" );
count = startupArgs.length;
for ( int i = 0; i < count; i++ )
{
// if the special startup args override the hostname, then don't
// specify it twice
if ( HOST_OPTION.equals( startupArgs[ i ] ) ) { skipHostName = true; }
}
al.addAll(Arrays.asList(getDefaultStartupArgs(skipHostName)));
al.addAll(Arrays.asList(startupArgs));
final String[] command = new String[ al.size() ];
al.toArray(command);
Process serverProcess = BaseTestCase.execJavaCmd(command);
return new SpawnedProcess(serverProcess, "SpawnedNetworkServer");
}
/**
* Returns the <code>Process</code> object for the server process or <code>null</code> if the
* network server does not run in a separate process
*/
public SpawnedProcess getServerProcess() {
return spawnedServer;
}
/**
* Stop the network server if it still
* appears to be running.
*/
protected void tearDown() throws Exception {
if (networkServerController != null) {
boolean running = false;
try {
networkServerController.ping();
running = true;
} catch (Exception e) {
}
Throwable failedShutdown = null;
if (running)
{
try {
networkServerController.shutdown();
} catch (Throwable t)
{
failedShutdown = t;
}
}
if ( serverOutput != null ) { serverOutput.close(); }
networkServerController = null;
serverOutput = null;
if (spawnedServer != null) {
// Destroy the process if a failed shutdown
// to avoid hangs running tests as the complete()
// waits for the process to complete.
spawnedServer.complete(getWaitTime());
spawnedServer = null;
}
// Throw an error to record the fact that the
// shutdown failed.
if (failedShutdown != null)
{
if (failedShutdown instanceof Exception)
{
// authentication failure is ok.
if (
!(failedShutdown instanceof SQLException)
)
{
throw (Exception) failedShutdown;
}
}
else
{
throw (Error) failedShutdown;
}
}
}
}
/**
* Get the default command arguments for booting the network server.
*/
public static String[] getDefaultStartupArgs( boolean skipHostName )
{
TestConfiguration config = TestConfiguration.getCurrent();
ArrayList<String> argsList = new ArrayList<String>();
argsList.add( "start" );
if ( !skipHostName )
{
argsList.add( HOST_OPTION );
argsList.add( config.getHostName() );
}
argsList.add( "-p" );
argsList.add( Integer.toString(config.getPort() ) );
if (config.getSsl() != null) {
argsList.add( "-ssl" );
argsList.add( config.getSsl( ) );
}
String[] retval = new String[ argsList.size() ];
argsList.toArray( retval );
return retval;
}
/* Network Server Control */
private NetworkServerControl networkServerController;
/*
* Utility methods related to controlling network server.
*/
/**
* Return a new NetworkServerControl for the current configuration.
*/
public static NetworkServerControl getNetworkServerControl()
throws Exception
{
TestConfiguration config = TestConfiguration.getCurrent();
final InetAddress host = InetAddress.getByName(config.getHostName());
final int port = config.getPort();
final String user = config.getUserName();
final String password = config.getUserPassword();
if (config.getSsl() == null) {
return new NetworkServerControl(host, port, user, password);
} else {
// This is a hack. A NetworkServerControl constructor with
// the needed interface to control sslMode (and possibly
// more) would be better.
String oldValue = BaseTestCase.getSystemProperty("derby.drda.sslMode");
BaseTestCase.setSystemProperty("derby.drda.sslMode", config.getSsl());
NetworkServerControl control
= new NetworkServerControl(host, port, user, password);
if (oldValue == null) {
BaseTestCase.removeSystemProperty("derby.drda.sslMode");
} else {
BaseTestCase.setSystemProperty("derby.drda.sslMode", oldValue);
}
return control;
}
}
/**
* Return a new NetworkServerControl for the current configuration.
* Use the port number specified.
* This method is not for general use - in most cases, the port
* should not be specified in the test, instead, the test framework
* will decide what is the best port number to use.
*/
public static NetworkServerControl getNetworkServerControl(int port)
throws Exception
{
TestConfiguration config = TestConfiguration.getCurrent();
final InetAddress host = InetAddress.getByName(config.getHostName());
final String user = config.getUserName();
final String password = config.getUserPassword();
return new NetworkServerControl(host, port, user, password);
}
/**
* Return a new NetworkServerControl for the current configuration.
* Use default values, i.e. port number and host are dependent on
* whatever settings are set in the environment (properties)
*/
public static NetworkServerControl getNetworkServerControlDefault()
throws Exception
{
TestConfiguration config = TestConfiguration.getCurrent();
final String user = config.getUserName();
final String password = config.getUserPassword();
return new NetworkServerControl(user, password);
}
/**
* Ping the server until it has started. Asserts a failure
* if the server has not started within sixty seconds.
*/
public static void waitForServerStart(NetworkServerControl networkServerController)
throws InterruptedException
{
if (!pingForServerStart(networkServerController)) {
fail(getTimeoutErrorMsg("network server to start"));
}
}
/**
* Set the number of milliseconds to wait before declaring server startup
* a failure.
*
*/
public static void setWaitTime( long newWaitTime )
{
waitTime = newWaitTime;
}
/**
* Set the number of milliseconds to wait before declaring server startup
* a failure back to the default value specified in this class.
*
*/
public static void setDefaultWaitTime()
{
waitTime = WAIT_TIME;
}
/**
* Ping server for upto sixty seconds. If the server responds
* in that time then return true, otherwise return false.
*
* @param networkServerController controller object for network server
* @param serverProcess the external process in which the server runs
* (could be <code>null</code>)
* @return true if server responds in time, false otherwise
*/
public static boolean pingForServerUp(
NetworkServerControl networkServerController, Process serverProcess,
boolean expectServerUp)
throws InterruptedException
{
//DERBY-6337(derbynet.ServerPropertiesTest.ttestSetPortPriority prints
// exception java.lang.Exception:
// DRDA_InvalidReplyTooShort.S:Invalidreply from network
// server:Insufficent data. but test passes)
//Sometimes, when server is coming down and a ping is sent to it, ping
// may get DRDA_InvalidReplyTooShort.S:Invalidreply rather than server
// is down depending on the timing of the server shutdown. If we do run
// into DRDA_InvalidReplyTooShort.S:Invalidreply, we will now send
// another ping after a little wait, and this time around we should
// get expected server down exception.
//Following boolean will be set to true if we get reply too short
// during the ping and it will try to ping again. But if we get
// the reply too short on that ping attempt as well, we will just
// print the exception on the console and conclude that server is
// down.
boolean alreadyGotReplyTooShort=false;
// If we expect the server to be or come up, then
// it makes sense to sleep (if ping unsuccessful), then ping
// and repeat this for the duration of wait-time, but stop
// when the ping is successful.
// But if we are pinging to see if the server is - or
// has come - down, we should do the opposite, stop if ping
// is unsuccessful, and repeat until wait-time if it is
final long startTime = System.currentTimeMillis();
while (true) {
try {
networkServerController.ping();
long elapsed = System.currentTimeMillis() - startTime;
if (expectServerUp) {
if (elapsed > 60000L) {
BaseTestCase.alarm(
"Very slow server startup: " + elapsed + " ms");
}
return true;
} else if (elapsed > waitTime) {
return true;
}
} catch (Throwable e) {
if ( !vetPing( e ) )
{
if ( !alreadyGotReplyTooShort &&
(e.getMessage().startsWith( "DRDA_InvalidReplyTooShort.S:" ) ) ){
alreadyGotReplyTooShort = true;
Thread.sleep(SLEEP_TIME);
continue;
}
e.printStackTrace( System.out );
// at this point, we don't have the usual "server not up
// yet" error. get out. at this point, you may have to
// manually kill the server.
return false;
}
if (expectServerUp){
if (System.currentTimeMillis() - startTime > waitTime)
return false;
}
// else, we got what we expected, done.
else
return false;
}
if (serverProcess != null) {
// if the server runs in a separate process, check whether the
// process is still alive
try {
int exitVal = serverProcess.exitValue();
// When exitValue() returns successfully, the server
// process must have terminated. No point in pinging the
// server anymore.
return false;
} catch (IllegalThreadStateException e) {
// This exception is thrown by Process.exitValue() if the
// process has not terminated. Keep on pinging the server.
} catch (Throwable t) {
// something unfortunate happened
t.printStackTrace( System.out );
return false;
}
}
Thread.sleep(SLEEP_TIME);
}
}
// return false if ping returns an error other than "server not up yet"
private static boolean vetPing( Throwable t )
{
if ( !t.getClass().getName().equals( "java.lang.Exception" ) ) { return false; }
return ( t.getMessage().startsWith( "DRDA_NoIO.S:" ) );
}
// return true if this is a drda error
private static boolean isDRDAerror( Throwable t )
{
if ( !t.getClass().getName().equals( "java.lang.Exception" ) ) { return false; }
return ( t.getMessage().startsWith( "DRDA" ) );
}
public static boolean pingForServerStart(NetworkServerControl control)
throws InterruptedException
{
return pingForServerUp(control, null, true);
}
/**
* Set the period before network server times out on start up based on the
* value passed in with property derby.tests.networkServerStartTimeout
* in seconds, or use the default.
* For example: with DEFAULT_WAIT_TIME set to 240000, i.e. 4 minutes,
* setting the property like so:
* <pre>
* -Dderby.tests.networkServerStartTimeout=600
* </pre>
* would extend the timeout to 10 minutes.
* If an invalid value is passed in (eg. 'abc') the calling test will fail
*/
public static long getWaitTime() {
long waitTime = DEFAULT_WAIT_TIME;
String waitString = BaseTestCase.getSystemProperty(
"derby.tests.networkServerStartTimeout");
if (waitString != null && waitString.length() != 0)
{
try {
waitTime = (Long.parseLong(waitString)*1000);
} catch (Exception e) {
BaseTestCase.fail(
"trouble setting WAIT_TIME from passed in property " +
"derby.tests.networkServerStartTimeout", e);
}
}
return waitTime;
}
/** Returns an error message for timeouts including the port and host. */
private static String getTimeoutErrorMsg(String failedAction, int port) {
TestConfiguration conf = TestConfiguration.getCurrent();
String host = conf.getHostName();
return "Timed out waiting for " +
failedAction + " (" + host + ":" + port + ")";
}
private static String getTimeoutErrorMsg(String failedAction) {
TestConfiguration conf = TestConfiguration.getCurrent();
int port = conf.getPort();
return getTimeoutErrorMsg(failedAction, port);
}
}