NIHVIVO-222 don't shut down Tomcat between sweets - just stop and start the app.

This commit is contained in:
jeb228 2010-08-24 20:46:18 +00:00
parent 49cbd0c171
commit d48efa96cf
10 changed files with 330 additions and 188 deletions

View file

@ -60,6 +60,9 @@ run - Run the tester.
deprecation="true" deprecation="true"
optimize="true" optimize="true"
source="1.6"> source="1.6">
<classpath>
<pathelement location="${lib.dir}/commons-httpclient-3.1.jar" />
</classpath>
</javac> </javac>
</target> </target>
@ -120,6 +123,9 @@ run - Run the tester.
<classpath> <classpath>
<pathelement location="${build.dir}" /> <pathelement location="${build.dir}" />
<pathelement location="${source.dir}" /> <pathelement location="${source.dir}" />
<pathelement location="${lib.dir}/commons-httpclient-3.1.jar" />
<pathelement location="${lib.dir}/commons-logging-1.1.1.jar" />
<pathelement location="${lib.dir}/commons-codec-1.3.jar" />
</classpath> </classpath>
<arg file="${acceptance.properties.file}" /> <arg file="${acceptance.properties.file}" />
<arg value="${acceptance.interactive.arg}" /> <arg value="${acceptance.interactive.arg}" />

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -2,6 +2,7 @@
package edu.cornell.mannlib.vitro.utilities.testrunner; package edu.cornell.mannlib.vitro.utilities.testrunner;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -9,6 +10,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Reader;
/** /**
* Some utility methods for dealing with files and directories. * Some utility methods for dealing with files and directories.
@ -137,6 +139,26 @@ public class FileHelper {
} }
} }
/**
* Suck all the data from a {@link Reader} into a {@link String}.
*/
public static String readAll(Reader reader) throws IOException {
StringBuilder result = new StringBuilder();
BufferedReader buffered = new BufferedReader(reader);
char[] chunk = new char[4096];
int howMany;
try {
while (-1 != (howMany = buffered.read(chunk))) {
result.append(chunk, 0, howMany);
}
} finally {
reader.close();
}
return result.toString();
}
/** No need to instantiate this, since all methods are static. */ /** No need to instantiate this, since all methods are static. */
private FileHelper() { private FileHelper() {
// Nothing to initialize. // Nothing to initialize.

View file

@ -29,13 +29,6 @@ public class ModelCleaner {
this.tomcatController = tomcatController; this.tomcatController = tomcatController;
sanityCheck(); sanityCheck();
try {
tomcatController.stopTheWebapp();
tomcatController.startTheWebapp();
} catch (CommandRunnerException e) {
throw new FatalException(
"sanityCheck: Failed to stop and start Tomcat.", e);
}
} }
private void sanityCheck() { private void sanityCheck() {

View file

@ -10,19 +10,17 @@ import java.util.Properties;
* model. * model.
*/ */
public class ModelCleanerProperties { public class ModelCleanerProperties {
public static final String PROP_TOMCAT_START_COMMAND = "tomcat_start_command"; public static final String PROP_VIVO_WEBAPP_NAME = "vivo_webapp_name";
public static final String PROP_TOMCAT_START_DELAY = "tomcat_start_delay"; public static final String PROP_TOMCAT_MANAGER_USERNAME = "tomcat_manager_username";
public static final String PROP_TOMCAT_STOP_COMMAND = "tomcat_stop_command"; public static final String PROP_TOMCAT_MANAGER_PASSWORD = "tomcat_manager_password";
public static final String PROP_TOMCAT_STOP_DELAY = "tomcat_stop_delay";
public static final String PROP_MYSQL_USERNAME = "mysql_username"; public static final String PROP_MYSQL_USERNAME = "mysql_username";
public static final String PROP_MYSQL_PASSWORD = "mysql_password"; public static final String PROP_MYSQL_PASSWORD = "mysql_password";
public static final String PROP_MYSQL_DB_NAME = "mysql_db_name"; public static final String PROP_MYSQL_DB_NAME = "mysql_db_name";
public static final String PROP_WEBAPP_DIRECTORY = "vivo_webapp_directory"; public static final String PROP_WEBAPP_DIRECTORY = "vivo_webapp_directory";
private final String tomcatStartCommand; private final String vivoWebappName;
private final int tomcatStartDelay; private final String tomcatManagerUsername;
private final String tomcatStopCommand; private final String tomcatManagerPassword;
private final int tomcatStopDelay;
private final String mysqlUsername; private final String mysqlUsername;
private final String mysqlPassword; private final String mysqlPassword;
private final String mysqlDbName; private final String mysqlDbName;
@ -33,15 +31,11 @@ public class ModelCleanerProperties {
* reasonable. * reasonable.
*/ */
public ModelCleanerProperties(Properties props) { public ModelCleanerProperties(Properties props) {
this.tomcatStartCommand = getRequiredProperty(props, this.vivoWebappName = checkWebappName(props);
PROP_TOMCAT_START_COMMAND); this.tomcatManagerUsername = getRequiredProperty(props,
this.tomcatStartDelay = getRequiredIntegerProperty(props, PROP_TOMCAT_MANAGER_USERNAME);
PROP_TOMCAT_START_DELAY); this.tomcatManagerPassword = getRequiredProperty(props,
PROP_TOMCAT_MANAGER_PASSWORD);
this.tomcatStopCommand = getRequiredProperty(props,
PROP_TOMCAT_STOP_COMMAND);
this.tomcatStopDelay = getRequiredIntegerProperty(props,
PROP_TOMCAT_STOP_DELAY);
this.mysqlUsername = getRequiredProperty(props, PROP_MYSQL_USERNAME); this.mysqlUsername = getRequiredProperty(props, PROP_MYSQL_USERNAME);
this.mysqlPassword = getRequiredProperty(props, PROP_MYSQL_PASSWORD); this.mysqlPassword = getRequiredProperty(props, PROP_MYSQL_PASSWORD);
@ -50,22 +44,6 @@ public class ModelCleanerProperties {
this.webappDirectory = confirmWebappDirectory(props); this.webappDirectory = confirmWebappDirectory(props);
} }
public String getTomcatStartCommand() {
return tomcatStartCommand;
}
public int getTomcatStartDelay() {
return tomcatStartDelay;
}
public String getTomcatStopCommand() {
return tomcatStopCommand;
}
public int getTomcatStopDelay() {
return tomcatStopDelay;
}
public String getMysqlUsername() { public String getMysqlUsername() {
return mysqlUsername; return mysqlUsername;
} }
@ -82,6 +60,18 @@ public class ModelCleanerProperties {
return webappDirectory; return webappDirectory;
} }
public String getVivoWebappName() {
return vivoWebappName;
}
public String getTomcatManagerUsername() {
return tomcatManagerUsername;
}
public String getTomcatManagerPassword() {
return tomcatManagerPassword;
}
/** /**
* Get the value for this property. If there isn't one, or if it's empty, * Get the value for this property. If there isn't one, or if it's empty,
* complain. * complain.
@ -95,14 +85,19 @@ public class ModelCleanerProperties {
return value; return value;
} }
private int getRequiredIntegerProperty(Properties props, String key) { /**
String value = getRequiredProperty(props, key); * The website URL must end with the webapp name.
try { */
return Integer.parseInt(value.trim()); private String checkWebappName(Properties props) {
} catch (NumberFormatException e) { String websiteUrl = getRequiredProperty(props,
throw new IllegalArgumentException("Property value for '" + key SeleniumRunnerParameters.PROP_WEBSITE_URL);
+ "' is not a valid integer: " + value); String webappName = getRequiredProperty(props, PROP_VIVO_WEBAPP_NAME);
if (!websiteUrl.endsWith(webappName)) {
throw new IllegalArgumentException("The " + PROP_VIVO_WEBAPP_NAME
+ " must be the last item in the "
+ SeleniumRunnerParameters.PROP_WEBSITE_URL);
} }
return webappName;
} }
/** /**
@ -130,10 +125,9 @@ public class ModelCleanerProperties {
} }
public String toString() { public String toString() {
return "\n tomcatStartCommand: " + tomcatStartCommand return "\n vivoWebappName: " + vivoWebappName
+ "\n tomcatStartDelay: " + tomcatStartDelay + "\n tomcatManagerUsername: " + tomcatManagerUsername
+ "\n tomcatStopCommand: " + tomcatStopCommand + "\n tomcatManagerPassword: " + tomcatManagerPassword
+ "\n tomcatStopDelay: " + tomcatStopDelay
+ "\n mysqlUsername: " + mysqlUsername + "\n mysqlUsername: " + mysqlUsername
+ "\n mysqlPassword: " + mysqlPassword + "\n mysqlPassword: " + mysqlPassword
+ "\n mysqlDbName: " + mysqlDbName + "\n mysqlDbName: " + mysqlDbName

View file

@ -2,140 +2,177 @@
package edu.cornell.mannlib.vitro.utilities.testrunner; package edu.cornell.mannlib.vitro.utilities.testrunner;
import java.util.ArrayList; import java.io.BufferedReader;
import java.util.List; import java.io.IOException;
import java.io.StringReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import edu.cornell.mannlib.vitro.utilities.testrunner.listener.Listener; import edu.cornell.mannlib.vitro.utilities.testrunner.listener.Listener;
import edu.cornell.mannlib.vitro.utilities.testrunner.tomcat.HttpHelper;
/** /**
* Start and stop the webapp, so we can clean the database. * Start and stop the webapp, so we can clean the database.
*/ */
public class TomcatController { public class TomcatController {
private static final Pattern PATTERN_WEBAPP_LISTING = Pattern
.compile("/(\\w+):(\\w+):");
private final SeleniumRunnerParameters parms; private final SeleniumRunnerParameters parms;
private final ModelCleanerProperties properties; private final ModelCleanerProperties properties;
private final Listener listener; private final Listener listener;
private final String tomcatBaseUrl;
private final String tomcatManagerUrl;
private final String tomcatManagerUsername;
private final String tomcatManagerPassword;
private final String webappName;
public TomcatController(SeleniumRunnerParameters parms) { public TomcatController(SeleniumRunnerParameters parms) {
this.parms = parms; this.parms = parms;
this.properties = parms.getModelCleanerProperties(); this.properties = parms.getModelCleanerProperties();
this.listener = parms.getListener(); this.listener = parms.getListener();
this.webappName = properties.getVivoWebappName();
this.tomcatBaseUrl = figureBaseUrl();
this.tomcatManagerUrl = this.tomcatBaseUrl + "/manager";
this.tomcatManagerUsername = properties.getTomcatManagerUsername();
this.tomcatManagerPassword = properties.getTomcatManagerPassword();
checkThatTomcatIsReady();
}
private String figureBaseUrl() {
String url = parms.getWebsiteUrl();
return url.substring(0, url.length() - webappName.length());
} }
/** /**
* Stop Tomcat and wait the prescribed number of seconds for it to clean up. * Insure that Tomcat is running and has the ability to start and stop VIVO.
*/ */
public void stopTheWebapp() throws CommandRunnerException { private void checkThatTomcatIsReady() {
String tomcatStopCommand = properties.getTomcatStopCommand(); HttpHelper hh = new HttpHelper();
int tomcatStopDelay = properties.getTomcatStopDelay();
CommandRunner runner = new CommandRunner(parms); // Is Tomcat responding?
if (!hh.getPage(tomcatBaseUrl)) {
listener.webappStopping(tomcatStopCommand); throw newHttpException(hh, "Tomcat does not respond");
runner.run(parseCommandLine(tomcatStopCommand));
int returnCode = runner.getReturnCode();
if (returnCode != 0) {
listener.webappStopFailed(returnCode);
// Throw no exception - this can happen if Tomcat isn't running.
} }
listener.webappWaitingForStop(tomcatStopDelay); // Does the manager respond?
try { hh.getPage(tomcatManagerUrl + "/list");
Thread.sleep(tomcatStopDelay * 1000L); if (hh.getStatus() == 404) {
} catch (InterruptedException e) { throw newHttpException(hh,
// Just continue. "Tomcat manager application does not respond. "
+ "Is it installed?");
} }
listener.webappStopped(); // Do we have the correct authorization for the manager?
hh.getPage(tomcatManagerUrl + "/list", tomcatManagerUsername,
tomcatManagerPassword);
if (hh.getStatus() != 200) {
throw newHttpException(hh, "Failed to list Tomcat applications");
}
// Is the VIVO application running?
boolean running = isVivoRunning(hh.getResponseText());
if (running) {
stopTheWebapp();
}
// Be sure that we can start it.
startTheWebapp();
} }
/** /**
* Start Tomcat and wait for it to initialize. * Tell Tomcat to start the webapp. Check the response.
*/ */
public void startTheWebapp() { public void startTheWebapp() {
String tomcatStartCommand = properties.getTomcatStartCommand(); String startCommand = tomcatManagerUrl + "/start?path=/" + webappName;
int tomcatStartDelay = properties.getTomcatStartDelay(); listener.webappStarting(startCommand);
CommandRunner runner = new CommandRunner(parms); HttpHelper hh = new HttpHelper();
hh.getPage(startCommand, tomcatManagerUsername, tomcatManagerPassword);
listener.webappStarting(tomcatStartCommand); if ((hh.getStatus() != 200) || (!hh.getResponseText().startsWith("OK"))) {
try { listener.webappStartFailed(hh.getStatus());
runner.runAsBackground(parseCommandLine(tomcatStartCommand)); throw newHttpException(hh, "Failed to start the webapp '"
} catch (CommandRunnerException e) { + webappName + "'");
throw new FatalException(e);
}
// Can't check the return code because the process shouldn't end.
listener.webappWaitingForStart(tomcatStartDelay);
try {
Thread.sleep(tomcatStartDelay * 1000L);
} catch (InterruptedException e) {
// Just continue.
} }
listener.webappStarted(); listener.webappStarted();
} }
/** /**
* A command line must be broken into separate arguments, where arguments * Tell Tomcat to stop the webapp. Check the response.
* are delimited by blanks unless the blank (and the argument) is enclosed
* in quotes.
*/ */
static List<String> parseCommandLine(String commandLine) { public void stopTheWebapp() {
List<String> pieces = new ArrayList<String>(); String stopCommand = tomcatManagerUrl + "/stop?path=/" + webappName;
StringBuilder piece = null; listener.webappStopping(stopCommand);
boolean inDelimiter = true;
boolean inQuotes = false; HttpHelper hh = new HttpHelper();
for (int i = 0; i < commandLine.length(); i++) { hh.getPage(stopCommand, tomcatManagerUsername, tomcatManagerPassword);
char thisChar = commandLine.charAt(i);
if ((thisChar == ' ') && !inQuotes) { if ((hh.getStatus() != 200) || (!hh.getResponseText().startsWith("OK"))) {
if (inDelimiter) { listener.webappStopFailed(hh.getStatus());
// No effect. throw newHttpException(hh, "Failed to stop the webapp '"
} else { + webappName + "'");
inDelimiter = true;
pieces.add(piece.toString());
}
} else if (thisChar == '"') {
// Quotes are not carried into the parsed strings.
inQuotes = !inQuotes;
} else { // Not a blank or a quote.
if (inDelimiter) {
inDelimiter = false;
piece = new StringBuilder();
}
piece.append(thisChar);
}
} }
// There is an implied delimiter at the end of the command line. listener.webappStopped();
if (!inDelimiter) {
pieces.add(piece.toString());
} }
// Quotes must appear in pairs /**
if (inQuotes) { * Is the VIVO application listed, and is it running?
throw new IllegalArgumentException( */
"Command line contains mismatched quotes: " + commandLine); private boolean isVivoRunning(String responseText) {
boolean found = false;
boolean running = false;
BufferedReader r = new BufferedReader(new StringReader(responseText));
try {
String line;
while (null != (line = r.readLine())) {
Matcher m = PATTERN_WEBAPP_LISTING.matcher(line);
if (m.find()) {
if (this.webappName.equals(m.group(1))) {
found = true;
if ("running".equals(m.group(2))) {
running = true;
}
break;
}
}
}
r.close();
} catch (IOException e) {
// Can't happen when reading from a string.
e.printStackTrace();
} }
return pieces; if (!found) {
throw new FatalException("Webapp '" + this.webappName
+ "' not found in Tomcat's list of webapps: \n"
+ responseText);
}
return running;
}
/**
* Generate a {@link FatalException} that contains a bunch of info from the
* {@link HttpHelper}.
*/
private FatalException newHttpException(HttpHelper hh, String text) {
return new FatalException(text + " status is " + hh.getStatus()
+ ", response text is '" + hh.getResponseText() + "'");
} }
/** /**
* The run is finished. Do we need to do anything? * The run is finished. Do we need to do anything?
*/ */
public void cleanup() { public void cleanup() {
// If we've been starting and stopping Tomcat, // Leave the webapp running.
// stop it one more time.
if (parms.isCleanModel()) {
try {
stopTheWebapp();
} catch (CommandRunnerException e) {
throw new FatalException(e);
}
}
} }
} }

View file

@ -0,0 +1,141 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner.tomcat;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.auth.BasicScheme;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import edu.cornell.mannlib.vitro.utilities.testrunner.FileHelper;
/**
* TODO
*/
public class HttpHelper {
private String responseText;
private int status = -1;
private Throwable exception;
/**
* Read the page at the specified URL, and set the response text and status.
*/
public boolean getPage(String url) {
this.responseText = null;
this.status = -1;
this.exception = null;
HttpClient httpClient = new HttpClient();
HttpMethod method = new GetMethod(url);
Reader reader = null;
try {
method.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
method.getParams().setSoTimeout(60000);
httpClient.executeMethod(method);
this.status = method.getStatusCode();
reader = new InputStreamReader(method.getResponseBodyAsStream(),
Charset.forName("UTF-8"));
this.responseText = FileHelper.readAll(reader);
return true;
} catch (IOException e) {
this.exception = e;
return false;
} finally {
method.releaseConnection();
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* Read the page at the specified URL, performing authentication with the
* specified username and password, and set the response text and status.
*/
public boolean getPage(String url, String username, String password) {
responseText = null;
status = -1;
this.exception = null;
HttpClient httpClient = new HttpClient();
httpClient.getState().setCredentials(new AuthScope(null, -1, null, "basic"),
new UsernamePasswordCredentials(username, password));
HttpMethod method = new GetMethod(url);
Reader reader = null;
try {
method.getParams().setCookiePolicy(CookiePolicy.IGNORE_COOKIES);
method.getParams().setSoTimeout(60000);
httpClient.executeMethod(method);
this.status = method.getStatusCode();
reader = new InputStreamReader(method.getResponseBodyAsStream(),
Charset.forName("UTF-8"));
responseText = FileHelper.readAll(reader);
return true;
} catch (IOException e) {
this.exception = e;
return false;
} finally {
method.releaseConnection();
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public int getStatus() {
return status;
}
public String getResponseText() {
return responseText;
}
public Throwable getException() {
return exception;
}
public static class HttpHelperException extends Exception {
public HttpHelperException() {
}
public HttpHelperException(String message, Throwable cause) {
super(message, cause);
}
public HttpHelperException(String message) {
super(message);
}
public HttpHelperException(Throwable cause) {
super(cause);
}
}
}

View file

@ -1,51 +0,0 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testrunner;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
/**
* TODO
*/
public class TomcatControllerTest {
// ----------------------------------------------------------------------
// Tests for parseCommandLine()
// ----------------------------------------------------------------------
@Test
public void oneArgument() {
assertExpectedParsing("oneArgument", "oneArgument");
}
@Test
public void multipleArguments() {
assertExpectedParsing("more than one", "more", "than", "one");
}
@Test
public void quotedArgument() {
assertExpectedParsing("contains \"quoted blank\" string", "contains",
"quoted blank", "string");
}
@Test(expected = IllegalArgumentException.class)
public void mismatchedQuotes() {
assertExpectedParsing("contains mismatched \"quote");
}
@Test
public void emptyLine() {
assertExpectedParsing("");
}
private void assertExpectedParsing(String commandLine,
String... expectedPieces) {
assertEquals("parse", Arrays.asList(expectedPieces),
TomcatController.parseCommandLine(commandLine));
}
}