EJBTHREE-1396 MockServer must report startup / shutdown
jaikiran Nov 5, 2008 8:02 AMI have introduced a MockServerMonitor which has APIs for waiting for the MockServer to startup (and shutdown). It internally uses sockets to communicate with the MockServer which runs in a separate JVM.
* The RemoteAccessTestCase will use the startMonitoring() API on the MockServerMonitor to initialize the monitor.
* Later after the test case creates and new "process" to start the server, it invokes the waitForServerStartup() API (in place of the Thread.sleep()) which is a blocking call and waits until the MockServer starts
* The MockServer inturn when starting up (and at other important state changes) sends out notifications on the socket (which is monitored by the MockServerMonitor) about its status.
* The MockServer will send notifications only if monitoring is enabled, which is decided by the arguments passed to the MockServer.
Here are the changes that were introduced as part of this:
(Changes to) MockServer:
Index: src/test/java/org/jboss/ejb3/test/proxy/remoteaccess/MockServer.java =================================================================== --- src/test/java/org/jboss/ejb3/test/proxy/remoteaccess/MockServer.java (revision 80547) +++ src/test/java/org/jboss/ejb3/test/proxy/remoteaccess/MockServer.java (working copy) @@ -1,6 +1,11 @@ package org.jboss.ejb3.test.proxy.remoteaccess; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; import java.net.URL; +import java.net.UnknownHostException; import org.jboss.aop.AspectManager; import org.jboss.aop.AspectXmlLoader; @@ -13,6 +18,7 @@ import org.jboss.ejb3.test.proxy.common.ejb.sfsb.MyStatefulBean; import org.jboss.ejb3.test.proxy.common.ejb.slsb.MyStatelessBean; import org.jboss.logging.Logger; +import org.jboss.xb.binding.metadata.AddMethodMetaData; /** * MockServer @@ -35,6 +41,18 @@ private static MockServer server; private static final String FILENAME_EJB3_INTERCEPTORS_AOP = "ejb3-interceptors-aop.xml"; + + public static int SERVER_STARTING = 1; + + public static int SERVER_STARTED = 2; + + public static int SERVER_STOPPING = 9; + + public static int SERVER_STOPPED = 10; + + private Socket socket; + + private int monitoringPort = -1; // --------------------------------------------------------------------------------|| // Instance Members ---------------------------------------------------------------|| @@ -72,7 +90,7 @@ { // Assert test class passed in - assert args.length == 1 : "String fully-qualified name of test class is the required first argument"; + assert args.length > 0 : "(Minimally) String fully-qualified name of test class is the required first argument"; // Get Test Class String testClassname = args[0]; @@ -88,6 +106,10 @@ // Create a new Launcher MockServer launcher = new MockServer(testClass); + // If any monitoring port is specified, then set the port accordingly + if (args.length > 1) { + launcher.addMonitoringSupport(Integer.parseInt(args[1])); + } MockServer.setServer(launcher); // Initialize the launcher in a new Thread + + /** + * Initializes this {@link MockServer} to support monitoring + * + * @param port The port on which the status will be sent + */ + protected void addMonitoringSupport(int port) + { + this.monitoringPort = port; + try + { + this.socket = new Socket(InetAddress.getByName("localhost"),this.monitoringPort); + log.info("MockServer will send notifications to monitor on port " + this.monitoringPort); + } + catch (Exception e) + { + throw new RuntimeException("Could not add monitoring support to the MockServer: ",e); + } + + + + } + + /** + * Publishes a status to the <code>socket</code> <br/> + * This status can then be used by any "monitors" + * + * @param status The status to publish + */ + protected void publishStatus(int status) + { + DataOutputStream dataOutputStream; + try + { + dataOutputStream = new DataOutputStream(this.socket.getOutputStream()); + dataOutputStream.writeInt(status); + } + catch (IOException ioe) + { + throw new RuntimeException("Error while sending status = " + status,ioe); + } + + } + /** + * Returns true if this {@link MockServer} supports monitoring. + * Else returns false + * + * @return + */ + protected boolean hasMonitoringSupport() + { + if (this.monitoringPort != -1) + { + return true; + } + return false; } - // --------------------------------------------------------------------------------|| // Inner Classes ------------------------------------------------------------------|| // --------------------------------------------------------------------------------|| @@ -185,13 +263,38 @@ public void run() { // Initialize + boolean initialized = false; try { + // notify that the server is starting + if (this.getLauncher().hasMonitoringSupport()) { + this.getLauncher().publishStatus(SERVER_STARTING); + } + this.getLauncher().initialize(); + initialized = true; } catch (Throwable e) { throw new RuntimeException("Could not initialize " + this.getLauncher(), e); + } finally + { + if (this.getLauncher().hasMonitoringSupport()) + { + if (initialized) + { + // if successfully initialized then publish a "Started" notification + // This notification could then be used by the "monitor" + this.getLauncher().publishStatus(SERVER_STARTED); + } + else + { + // set the status to stopped + // TODO: Do we need a separate status like "NOT_STARTED" in + // addition to "STOPPED" + this.getLauncher().publishStatus(SERVER_STOPPED); + } + } } // Run @@ -230,6 +333,24 @@ public void run() { getServer().bootstrap.shutdown(); + try { + if (getServer().hasMonitoringSupport()) + { + getServer().publishStatus(SERVER_STOPPED); + } + } finally { + if (getServer().socket != null) { + try + { + getServer().socket.close(); + } + catch (IOException ioe) + { + // Ignore + log.debug("Error closing socket during shutdown of MockServer: " + ioe); + } + } + } } } @@ -267,4 +388,4 @@ MockServer.server = server; } -} \ No newline at end of file +}
The new MockServerMonitor:
Index: src/test/java/org/jboss/ejb3/test/proxy/remoteaccess/MockServerMonitor.java =================================================================== --- src/test/java/org/jboss/ejb3/test/proxy/remoteaccess/MockServerMonitor.java (revision 0) +++ src/test/java/org/jboss/ejb3/test/proxy/remoteaccess/MockServerMonitor.java (revision 0) @@ -0,0 +1,212 @@ +/* + * 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 file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY 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 along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.ejb3.test.proxy.remoteaccess; + +import java.io.DataInputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.jboss.logging.Logger; + +/** + * MockServerMonitor + * + * Monitors the startup/shutdown of the {@link MockServer} <br/> + * + * Internally creates a {@link ServerSocket} and listens for the + * {@link MockServer} to post its status. + * + * @author Jaikiran Pai + * @version $Revision: $ + */ +public class MockServerMonitor +{ + + /** + * Instance of logger + */ + private static Logger logger = Logger.getLogger(MockServerMonitor.class); + + /** + * The port number on which the {@link MockServer} + * will be monitored for status + */ + private int port; + + /** + * Localhost + */ + private InetAddress localServer; + + /** + * Flag to indicate whether the monitor has been initialized + */ + private boolean inited; + + /** + * Used for communicating with the {@link MockServer} which runs + * in a separate JVM + */ + private ServerSocket serverSocket; + + /** + * Constructor + * + * @param port + */ + public MockServerMonitor(int port) + { + this.port = port; + try + { + this.localServer = InetAddress.getByName("localhost"); + } + catch (UnknownHostException unhe) + { + String msg = "Could not create the mockserver monitor. Monitoring will be disabled."; + logger.error(msg, unhe); + throw new RuntimeException(unhe); + } + } + + /** + * Initialize for monitoring the {@link MockServer} <br/> + * + * This will create a {@link ServerSocket} to listen on the + * <code>port</code> passed to the {@link MockServerMonitor#MockServerMonitor(int)} + * constructor + */ + public void startMonitoring() + { + try + { + this.serverSocket = new ServerSocket(this.port, 1, this.localServer); + // set a flag indicating successful initialization + this.inited = true; + logger.info("Started to monitor MockServer on " + this.localServer + ":" + this.port); + } + catch (IOException ioe) + { + throw new RuntimeException("Could not start monitoring the MockServer on " + this.port, ioe); + + } + } + + /** + * Stop monitoring the {@link MockServer} <br/> + * + * Stops the {@link ServerSocket} and does any related + * cleanup + */ + public void stopMonitoring() + { + if (this.serverSocket == null) + { + // do nothing + return; + } + try + { + this.serverSocket.close(); + this.inited = false; + } + catch (IOException ioe) + { + throw new RuntimeException("Could not stop monitoring the MockServer on " + this.port, ioe); + + } + } + + /** + * Wait for the {@link MockServer} to startup + */ + public void waitForServerStartup() + { + if (inited) + { + Socket client = null; + try + { + logger.debug("Connecting... " + this.port); + + // We need to add a timeout to the monitoring, to handle cases where + // the control probably never reached a point in MockServer where it + // could allow the monitor to connect. We wouldn't want this monitor + // to wait forever. + + // the timeout can be made configurable + this.serverSocket.setSoTimeout(10000); + // Accept the MockServer connection + client = this.serverSocket.accept(); + + logger.debug("Connected... " + this.port); + + DataInputStream dataInputStream = new DataInputStream(client.getInputStream()); + int mockServerStatus = -1; + // Start receiving the status + while (mockServerStatus != MockServer.SERVER_STARTED) + { + logger.debug("Reading MockServer status on port " + this.port); + mockServerStatus = dataInputStream.readInt(); + logger.debug("MockServer returned status = " + mockServerStatus); + } + logger.info("MockServer is in started state"); + return; + + } + catch (IOException ioe) + { + logger.error("Error while monitoring the MockServer for startup on port " + this.port, ioe); + throw new RuntimeException("MockServer is probably not started: ", ioe); + } + finally + { + // house-keeping + if (client != null) + { + try + { + client.close(); + } + catch (IOException e) + { + // Ignore + logger.debug("Could not close socket"); + } + } + + } + } + } + + /** + * Wait for the {@link MockServer} to completely shutdown + */ + public void waitForServerShutdown() + { + // No implementation yet. The testcase currently does not + // wait for shutdown. If required, we can add the implementation + } +}
(Changes to the) RemoteAccessTestCase:
Index: src/test/java/org/jboss/ejb3/test/proxy/remoteaccess/unit/RemoteAccessTestCase.java =================================================================== --- src/test/java/org/jboss/ejb3/test/proxy/remoteaccess/unit/RemoteAccessTestCase.java (revision 80547) +++ src/test/java/org/jboss/ejb3/test/proxy/remoteaccess/unit/RemoteAccessTestCase.java (working copy) @@ -37,6 +37,7 @@ import org.jboss.ejb3.test.proxy.common.ejb.slsb.MyStatelessRemote; import org.jboss.ejb3.test.proxy.remoteaccess.JndiPropertiesToJndiRemotePropertiesHackCl; import org.jboss.ejb3.test.proxy.remoteaccess.MockServer; +import org.jboss.ejb3.test.proxy.remoteaccess.MockServerMonitor; import org.jboss.logging.Logger; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -82,6 +83,8 @@ private static final String JNDI_NAME_SFSB_REMOTE = "MyStatefulBean/remote"; private static Process remoteProcess; + + private static MockServerMonitor mockServerMonitor; private static Context context; @@ -181,11 +184,24 @@ // Replace the CL Thread.currentThread().setContextClassLoader(oldLoader); + // Start the MockServerMonitor + // TODO: Make the port configurable + int monitoringPort = 12345; + mockServerMonitor = new MockServerMonitor(monitoringPort); + mockServerMonitor.startMonitoring(); + // Start Server - RemoteAccessTestCase.invokeRemoteMockServerProcess(RemoteAccessTestCase.class.getName()); + RemoteAccessTestCase.invokeRemoteMockServerProcess(new String[] {RemoteAccessTestCase.class.getName(),Integer.toString(monitoringPort)}); // Wait for Server to start - Thread.sleep(5000); + log.info("Waiting for MockServer to start"); + long start = System.currentTimeMillis(); + + mockServerMonitor.waitForServerStartup(); + + long end = System.currentTimeMillis(); + log.info("MockServer started in " + (end-start) + " milli sec."); + } /** @@ -202,6 +218,8 @@ Process p = RemoteAccessTestCase.getRemoteProcess(); p.destroy(); + mockServerMonitor.stopMonitoring(); + } // --------------------------------------------------------------------------------|| @@ -214,7 +232,7 @@ * @param argument * @throws Throwable */ - protected static void invokeRemoteMockServerProcess(String argument) throws Throwable + protected static void invokeRemoteMockServerProcess(String[] arguments) throws Throwable { // Get the current System Properties and Environment Variables String javaHome = System.getenv(RemoteAccessTestCase.ENV_VAR_JAVAHOME); @@ -243,7 +261,8 @@ command.append(File.separatorChar); command.append(RemoteAccessTestCase.EXECUTABLE_JAVA); command.append(" -cp "); // Classpath - + command.append("\""); + command.append(classes); command.append(File.pathSeparatorChar); command.append(testClasses); @@ -251,10 +270,16 @@ command.append(conf); command.append(File.pathSeparatorChar); command.append(depCp); // Dependency CP + command.append("\""); + command.append(" -ea "); // Enable Assertions command.append(MockServer.class.getName()); command.append(' '); - command.append(argument); // Argument + for (int i=0; i < arguments.length; i++) { + command.append(arguments); // Argument + command.append(' '); + } + // Create a Remote Launcher String cmd = command.toString();
Any review comments/suggestions are welcome. I'll attach a patch to the JIRA if this looks like a valid start at implementing this enhancement.