/* * JBoss, Home of Professional Open Source * Copyright 2005-2008, Red Hat Middleware LLC, and individual contributors * by the @authors tag. See the copyright.txt 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.test.messaging.tools; import java.io.BufferedReader; import java.io.InputStreamReader; import java.rmi.Naming; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.management.Notification; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.transaction.UserTransaction; import org.jboss.kernel.spi.deployment.KernelDeployment; import org.jboss.messaging.core.logging.Logger; import org.jboss.test.messaging.tools.container.InVMInitialContextFactory; import org.jboss.test.messaging.tools.container.LocalTestServer; import org.jboss.test.messaging.tools.container.NotificationListenerID; import org.jboss.test.messaging.tools.container.RMITestServer; import org.jboss.test.messaging.tools.container.RemoteInitialContextFactory; import org.jboss.test.messaging.tools.container.Server; import org.jboss.test.messaging.tools.container.ServiceAttributeOverrides; /** * Collection of static methods to use to start/stop and interact with the in-memory JMS server. It * is also use to start/stop a remote server. * * @author <a href="mailto:ovidiu@feodorov.com">Ovidiu Feodorov</a> * @author <a href="mailto:tim.fox@jboss.com">Tim Fox</a> * @version <tt>$Revision$</tt> * <p/> * $Id$ */ public class ServerManagement { // Constants ----------------------------------------------------- public static final int MAX_SERVER_COUNT = 10; // logging levels used by the remote client to forward log output on a remote server public static int FATAL = 0; public static int ERROR = 1; public static int WARN = 2; public static int INFO = 3; public static int DEBUG = 4; public static int TRACE = 5; public static final String DEFAULT_QUEUE_CONTEXT = "/queue"; public static final String DEFAULT_TOPIC_CONTEXT = "/topic"; // Static -------------------------------------------------------- private static Logger log = Logger.getLogger(ServerManagement.class); private static List<Server> servers = new ArrayList<Server>(); // Map<NotificationListener - NotificationListenerPoller> private static Map notificationListenerPollers = new HashMap(); public static boolean isLocal() { return !"true".equals(System.getProperty("remote")); } public static boolean isRemote() { return !isLocal(); } public static boolean isClustered() { return "true".equals(System.getProperty("test.clustered")); } /** * May return null if the corresponding server is not initialized. */ public synchronized static Server getServer(int i) { return servers.get(i); } /** * Makes sure that a "hollow" TestServer (either local or remote, depending on the nature of the * test), exists and it's ready to be started. */ public static synchronized Server create(int i) throws Exception { if (isLocal()) { log.info("Attempting to create local server " + i); return new LocalTestServer(i); } else { //Need to spawn a new server - we DON'T use start-rmi-server any more, so we know if the servers[i] is null //the server is not there - killing a server sets servers[i] to null log.info("Attempting to create remote server " + i); return ServerManagement.spawn(i); } } public static void start(int i, String config, boolean clearDatabase) throws Exception { start(i, config, null, clearDatabase); } public static void start(int i, String config, ServiceAttributeOverrides attrOverrides, boolean clearDatabase) throws Exception { start(i, config, attrOverrides, clearDatabase, true); } /** * When this method correctly completes, the server (local or remote) is started and fully * operational (the service container and the server peer are created and started). */ public static void start(int i, String config, ServiceAttributeOverrides attrOverrides, boolean clearDatabase, boolean startMessagingServer) throws Exception { log.info("Attempting to start server " + i); //servers.get(i).start(config, attrOverrides, clearDatabase, startMessagingServer); /*Server s = create(i); s.start(config, attrOverrides, clearDatabase, startMessagingServer); */ log.info("server " + i + " started"); } public static synchronized boolean isStarted(int i) throws Exception { return servers.get(i).isStarted(); } public static void stop() throws Exception { stop(0); } /** * The method stops the local or remote server, bringing it to a "hollow" state. A stopped * server is identical with a server that has just been created, but not started. * * @return true if the server was effectively stopped, or false if the server was alreayd stopped * when the method was invoked. */ public static boolean stop(int i) throws Exception { return servers.get(i).stop(); } public static synchronized void kill(int i) throws Exception { log.info("Attempting to kill server " + i); if (i == 0) { //Cannot kill server 0 if there are any other servers since it has the rmi registry in it for (int j = 1; j < servers.size(); j++) { if (servers.get(j) != null) { throw new IllegalStateException("Cannot kill server 0, since server[" + j + "] still exists"); } } } if (i > servers.size()) { log.info("server " + i + " has not been created or has already been killed, so it cannot be killed"); } else { Server server = servers.get(i); log.info("invoking kill() on server " + i); try { server.kill(); } catch (Throwable t) { // This is likely to throw an exception since the server dies before the response is received } log.info("Waiting for server to die"); try { while (true) { server.ping(); log.debug("server " + i + " still alive ..."); Thread.sleep(100); } } catch (Throwable e) { //Ok } Thread.sleep(300); log.info("server " + i + " killed and dead"); } } /** * This method make sure that all servers that have been implicitely spawned when as a side * effect of create() and/or start() are killed. The method is important because a forked * ant junit task won't exit if processes created by it are still active. If you run tests * from ant, always call killSpawnedServers() in tearDown(). * <p/> * The servers created directed invoking spawn() are not subject to destroySpawnedServers(); they * need to be explicitely killed. * * @return a List<Integer> containing the indexes of the destroyed servers. */ public static synchronized List destroySpawnedServers() throws Exception { log.info("################# Destroying spawned servers****"); List destroyed = new ArrayList(); for (Server server : servers) { destroyed.add(new Integer(server.getServerID())); log.info("Killing spawned server " + server.getServerID()); try { server.kill(); } catch (Throwable t) { } } return destroyed; } /** * For a local test, is a noop, but for a remote test, the method call spawns a new VM */ private static synchronized Server spawn(final int i) throws Exception { if (isLocal()) { return null; } StringBuffer sb = new StringBuffer(); sb.append("java").append(' '); sb.append("-Xmx512M").append(' '); String remoteDebugIndex = "";//System.getProperty("test.remote.debug.index"); if (remoteDebugIndex != null) { //sb.append("-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006 "); } String objectStoreDir = System.getProperty("objectstore.dir"); if (objectStoreDir != null) { sb.append("-Dobjectstore.dir=" + objectStoreDir).append(" "); } String moduleOutput = System.getProperty("module.output"); if (moduleOutput == null) { moduleOutput = "./output"; } sb.append("-Dmodule.output=").append(moduleOutput).append(' '); String bindAddress = System.getProperty("test.bind.address"); if (bindAddress == null) { bindAddress = "localhost"; } sb.append("-Dtest.bind.address=").append(bindAddress).append(' '); //Use test.bind.address for the jgroups.bind_addr String jgroupsBindAddr = bindAddress; sb.append("-D").append(org.jgroups.Global.BIND_ADDR).append("=") .append(jgroupsBindAddr).append(' '); String database = System.getProperty("test.database"); if (database != null) { sb.append("-Dtest.database=").append(database).append(' '); } String serialization = System.getProperty("test.serialization"); if (serialization != null) { sb.append("-Dtest.serialization=").append(serialization).append(' '); } sb.append("-Dtest.server.index=").append(i).append(' '); String clustered = System.getProperty("test.clustered"); if (clustered != null && clustered.trim().length() == 0) { clustered = null; } if (clustered != null) { sb.append("-Dtest.clustered=").append(clustered).append(' '); } String remoting = System.getProperty("test.remoting"); if (remoting != null) { sb.append("-Dtest.remoting=").append(remoting).append(' '); } String groupName = System.getProperty("jboss.messaging.groupname"); log.info("******* GROUP NAME IS " + groupName); if (groupName != null) { sb.append("-Djboss.messaging.groupname=").append(groupName).append(' '); } String dataChannelUDPPort = System.getProperty("jboss.messaging.datachanneludpport"); log.info("*** data UDP port is " + dataChannelUDPPort); if (dataChannelUDPPort != null) { sb.append("-Djboss.messaging.datachanneludpport=").append(dataChannelUDPPort).append(' '); } String controlChannelUDPPort = System.getProperty("jboss.messaging.controlchanneludpport"); log.info("*** control UDP port is " + controlChannelUDPPort); if (controlChannelUDPPort != null) { sb.append("-Djboss.messaging.controlchanneludpport=").append(controlChannelUDPPort).append(' '); } String dataChannelUDPAddress = System.getProperty("jboss.messaging.datachanneludpaddress"); log.info("*** data UDP address is " + dataChannelUDPAddress); if (dataChannelUDPAddress != null) { sb.append("-Djboss.messaging.datachanneludpaddress=").append(dataChannelUDPAddress).append(' '); } String controlChannelUDPAddress = System.getProperty("jboss.messaging.controlchanneludpaddress"); log.info("*** control UDP address is " + controlChannelUDPAddress); if (controlChannelUDPAddress != null) { sb.append("-Djboss.messaging.controlchanneludpaddress=").append(controlChannelUDPAddress).append(' '); } sb.append("-Djava.naming.factory.initial=org.jboss.test.messaging.tools.container.InVMInitialContextFactory "); String ipttl = System.getProperty("jboss.messaging.ipttl"); log.info("*** ip_ttl is " + ipttl); if (ipttl != null) { sb.append("-Djboss.messaging.ipttl=").append(ipttl).append(' '); } String testLogfileSuffix = System.getProperty("test.logfile.suffix"); if (testLogfileSuffix == null) { testLogfileSuffix = "undefined-test-type"; } else { int pos; if ((pos = testLogfileSuffix.lastIndexOf("client")) != -1) { testLogfileSuffix = testLogfileSuffix.substring(0, pos) + "server"; } // We need to add the i even in the non manageConfirmations case since we can have multiple // non manageConfirmations servers testLogfileSuffix += i; } sb.append("-Dtest.logfile.suffix=").append(testLogfileSuffix).append(' '); String classPath = System.getProperty("java.class.path"); if (System.getProperty("os.name").equals("Linux")) { sb.append("-cp").append(" ").append(classPath).append(" "); } else { sb.append("-cp").append(" \"").append(classPath).append("\" "); } // As there is a problem with Multicast and JGroups on Linux (in certain JDKs) // The stack introduced by multiplexer might fail under Linux if we don't have this if (System.getProperty("os.name").equals("Linux")) { sb.append(" -Djava.net.preferIPv4Stack=true "); } sb.append("org.jboss.test.messaging.tools.container.RMITestServer"); String commandLine = sb.toString(); Process process = Runtime.getRuntime().exec(commandLine); log.trace("process: " + process); // if you ever need to debug the spawing process, turn this flag to true: String parameterVerbose = "true";//System.getProperty("test.spawn.verbose"); final boolean verbose = parameterVerbose != null && parameterVerbose.equals("true"); final BufferedReader rs = new BufferedReader(new InputStreamReader(process.getInputStream())); final BufferedReader re = new BufferedReader(new InputStreamReader(process.getErrorStream())); new Thread(new Runnable() { public void run() { try { String line; while ((line = rs.readLine()) != null) { if (verbose) { System.out.println("SERVER " + i + " STDOUT: " + line); } } } catch (Exception e) { log.error("exception", e); } } }, "Server " + i + " STDOUT reader thread").start(); new Thread(new Runnable() { public void run() { try { String line; while ((line = re.readLine()) != null) { if (verbose) { System.out.println("SERVER " + i + " STDERR: " + line); } } } catch (Exception e) { log.error("exception", e); } } }, "Server " + i + " STDERR reader thread").start(); // put the invoking thread on wait until the server is actually up and running and bound // in the RMI registry log.info("spawned server " + i + ", waiting for it to come online"); Server s = acquireRemote(500, i, true); log.info("Server contacted"); if (s == null) { log.error("Cannot contact newly spawned server " + i + ", most likely the attempt failed, timing out ..."); throw new Exception("Cannot contact newly spawned server " + i + ", most likely the attempt failed, timing out ..."); } return s; } public static KernelDeployment deploy(String resource) throws Throwable { return servers.get(0).deploy(resource); } public static void undeploy(KernelDeployment on) throws Throwable { servers.get(0).undeploy(on); } public static Object getAttribute(ObjectName on, String attribute) throws Exception { return getAttribute(0, on, attribute); } public static Object getAttribute(int serverIndex, ObjectName on, String attribute) throws Exception { return servers.get(serverIndex).getAttribute(on, attribute); } public static void setAttribute(ObjectName on, String name, String valueAsString) throws Exception { servers.get(0).setAttribute(on, name, valueAsString); } public static Object invoke(ObjectName on, String operationName, Object[] params, String[] signature) throws Exception { return servers.get(0).invoke(on, operationName, params, signature); } public static void addNotificationListener(int serverIndex, ObjectName on, NotificationListener listener) throws Exception { if (isLocal()) { // add the listener directly to the server servers.get(serverIndex).addNotificationListener(on, listener); } else { // is remote, need to poll NotificationListenerPoller p = new NotificationListenerPoller(servers.get(serverIndex), on, listener); synchronized (notificationListenerPollers) { notificationListenerPollers.put(listener, p); } new Thread(p, "Poller for " + Integer.toHexString(p.hashCode())).start(); } } public static void removeNotificationListener(int serverIndex, ObjectName on, NotificationListener listener) throws Exception { if (isLocal()) { // remove the listener directly servers.get(serverIndex).removeNotificationListener(on, listener); } else { // is remote NotificationListenerPoller p = null; synchronized (notificationListenerPollers) { p = (NotificationListenerPoller) notificationListenerPollers.remove(listener); } if (p != null) { // stop the polling thread p.stop(); } } } public static UserTransaction getUserTransaction() throws Exception { return servers.get(0).getUserTransaction(); } public static void log(int level, String text) { log(level, text, 0); } public static void log(int level, String text, int index) { if (isRemote()) { if (servers.get(index) == null) { log.debug("The remote server " + index + " has not been created yet " + "so this log won't make it to the server!"); return; } try { servers.get(index).log(level, text); } catch (Exception e) { log.error("failed to forward the logging request to the remote server", e); } } } public static void startServerPeer() throws Exception { startServerPeer(0, null, null); } /** * @param serverPeerID - if null, the jboss-service.xml value will be used. * @param defaultQueueJNDIContext - if null, the jboss-service.xml value will be used. * @param defaultTopicJNDIContext - if null, the jboss-service.xml value will be used. */ public static void startServerPeer(int serverPeerID, String defaultQueueJNDIContext, String defaultTopicJNDIContext) throws Exception { startServerPeer(serverPeerID, defaultQueueJNDIContext, defaultTopicJNDIContext, null); } /** * @param serverPeerID - if null, the jboss-service.xml value will be used. * @param defaultQueueJNDIContext - if null, the jboss-service.xml value will be used. * @param defaultTopicJNDIContext - if null, the jboss-service.xml value will be used. */ public static void startServerPeer(int serverPeerID, String defaultQueueJNDIContext, String defaultTopicJNDIContext, ServiceAttributeOverrides attrOverrids) throws Exception { servers.get(0).startServerPeer(serverPeerID, defaultQueueJNDIContext, defaultTopicJNDIContext, attrOverrids, false); } public static void stopServerPeer() throws Exception { servers.get(0).stopServerPeer(); } // public static boolean isServerPeerStarted() throws Exception // { // // return servers.get(0).isServerPeerStarted(); // } public static ObjectName getServerPeerObjectName() throws Exception { return servers.get(0).getServerPeerObjectName(); } // public static MessageStore getMessageStore() throws Exception // { // // return servers.get(0).getMessageStore(); // } // public static DestinationManager getDestinationManager() // throws Exception // { // // return servers.get(0).getDestinationManager(); // } // // public static StorageManager getPersistenceManager() // throws Exception // { // // return servers.get(0).getPersistenceManager(); // } public static void configureSecurityForDestination(String destName, String config) throws Exception { configureSecurityForDestination(0, destName, config); } public static void configureSecurityForDestination(int serverID, String destName, String config) throws Exception { //servers.get(0).configureSecurityForDestination(destName, config); } public static Hashtable getJNDIEnvironment() { return getJNDIEnvironment(0); } public static Hashtable getJNDIEnvironment(int serverIndex) { if (isLocal()) { return InVMInitialContextFactory.getJNDIEnvironment(serverIndex); } else { return RemoteInitialContextFactory.getJNDIEnvironment(serverIndex); } } public static Server acquireRemote(int initialRetries, int index, boolean quiet) { String name = "//localhost:" + RMITestServer.DEFAULT_REGISTRY_PORT + "/" + RMITestServer.RMI_SERVER_PREFIX + index; Server s = null; int retries = initialRetries; while (s == null && retries > 0) { int attempt = initialRetries - retries + 1; try { String msg = "trying to connect to the remote RMI server " + index + (attempt == 1 ? "" : ", attempt " + attempt); if (quiet) { log.debug(msg); } else { log.info(msg); } s = (Server) Naming.lookup(name); log.debug("connected to remote server " + index); } catch (Exception e) { log.debug("failed to get the RMI server stub, attempt " + (initialRetries - retries + 1), e); try { Thread.sleep(500); } catch (InterruptedException e2) { // OK } retries--; } } return s; } // Attributes ---------------------------------------------------- // Constructors -------------------------------------------------- // Public -------------------------------------------------------- // Package protected --------------------------------------------- // Protected ----------------------------------------------------- // Private ------------------------------------------------------- // private static JmsServer getJmsServer(int id) // { // try // { // if (isLocal()) // { // return servers.get(id).getJmsServer(); // } // else // { // return null; // } // } // catch (Exception e) // { // throw new RuntimeException(); // } // } // Inner classes ------------------------------------------------- private static long listenerIDCounter = 0; static class NotificationListenerPoller implements Runnable { public static final int POLL_INTERVAL = 500; private long id; private Server server; private NotificationListener listener; private volatile boolean running; private synchronized static long generateID() { return listenerIDCounter++; } NotificationListenerPoller(Server server, ObjectName on, NotificationListener listener) throws Exception { id = generateID(); this.server = server; server.addNotificationListener(on, new NotificationListenerID(id)); this.listener = listener; this.running = true; } public void run() { while (running) { try { List notifications = server.pollNotificationListener(id); for (Iterator i = notifications.iterator(); i.hasNext();) { Notification n = (Notification) i.next(); listener.handleNotification(n, null); } Thread.sleep(POLL_INTERVAL); } catch (Exception e) { log.error(e); stop(); } } } public void stop() { running = false; } } }