Modified: river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/SlaveHarness.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/SlaveHarness.java?rev=1634322&r1=1634321&r2=1634322&view=diff ============================================================================== --- river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/SlaveHarness.java (original) +++ river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/SlaveHarness.java Sun Oct 26 13:17:28 2014 @@ -1,644 +1,646 @@ -/* - * 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 com.sun.jini.qa.harness; - -import java.io.File; -import java.io.EOFException; -import java.io.InputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.net.ConnectException; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.Properties; -import java.util.StringTokenizer; -import java.util.jar.JarFile; - -/** - * A 'harness' which accepts work requests from a master over - * a socket. - * <p> - * Generation of the MasterTest and SlaveTest command lines: - * <p> - * There are 4 VMs which participate in the distributed test process: - * <ul> - * <li>the master harness - * <li>the slave harness - * <li>the master test - * <li>the slave test request handler - * <ul> - * All 4 of these VMs maintain an instance of QAConfig. Because the - * VMs are running on different systems, potentially with different - * versions of the jdk/jsk kits, the values which define installation - * directories for the master/slave tests are defined as system properties - * on the test VMs invoked by the master/slave harnesses. - * <p> - * When the MasterHarness and SlaveHarness classes are instantiated, - * they construct an instance of QAConfig. The constructor of QAConfig - * verifies the existance of the following system properties: - * <ul> - * <li>com.sun.jini.jsk.home - * <li>com.sun.jini.qa.home - * </ul> - * which are typically provided by the user config file. These values serve as - * the default values to apply to the master/slave test VMs. When the - * <code>MasterHarness</code> creates the master test VM, it obtains the command - * line from the <code>TestDescription</code>. When generating the command - * line, the TD temporarily sets override properties generated from the QAConfig - * getSystemProps("master") method. Any master overrides supplied (indirectly) - * through the environment file (provided by the -env command line option) will - * be used to build the command line. Typically, one or more of the installation - * system properties listed above will be replaced if the test is to use a - * non-default kit. The master harness always provides property definitions for - * all of these properties so that policy files and ConfigurationFile entries - * have access to these definitions. The MasterHarness passes a serialized copy - * of its QAConfig to the master test over its System.in stream; it is not - * necessary for the master test to construct a QAConfig from scratch. Because - * system property definitions override the user configuration file values when - * QAConfig searches for a property, the values set on the command line - * by the harness will take precedence. - * <p> - * When the <code>MasterHarness</code> submits a request to the slave - * harness to create a <code>SlaveTest</code>, it also passes - * a serialized copy of its QAConfig. The <code>SlaveHarness</code> - * generates a command line, this time using overrides resulting from - * calling the QAConfig getSystemProps("slave") method. The information - * required to generate the slave overrides are carried in the serialized - * QAConfig object, so the <code>MasterHarness</code> controls the - * environment in which the <code>SlaveTest</code> runs. - */ -class SlaveHarness { - - /** The port used for piping System.out/System.err to the master */ - final static int LOG_PORT=10001; - - /** The port used to accept request messages */ - final static int REQUEST_PORT=10002; - - /** the logger */ - private static Logger logger = - Logger.getLogger("com.sun.jini.qa.harness"); - - /** - * The config object used directly by this class. It's primary - * purpose is to establish default values for installation - * properties such as com.sun.jini.jsk.home. The config used - * by the request handler vm is provided by the master harness - * and may provide overrides for the installation properties. - */ - QAConfig config; - - /** The list of slaves participating in this test run */ - private static ArrayList slaveList = new ArrayList(); - - /** Data structure holding slave info */ - private static class SlaveData { - String name; - InetAddress addr; - Socket logSocket; - Pipe pipe; - }; - - /** - * Called by the master harness to connect to all slave harnesses - * to be used by the test. If the test property - * com.sun.jini.qa.harness.testhosts specifies any slave harnesses, - * create the pipes to capture any output from the slave harnesses - * into the test log. Failure to connect to any slave within 5 - * minutes (XXX parameterize) results in a TestException being - * thrown, aborting the test run. - * - * @throws TestException on connection timeout - */ - static void connect() throws TestException { - ArrayList hostList = QAConfig.getConfig().getHostList(); - if (hostList.size() < 2) { - return; - } - // connect to all slaves and build slaveList - for (int i = 1; i < hostList.size(); i++) { - String host = (String) hostList.get(i); - SlaveData slave = new SlaveData(); - slave.name = host; - connectToSlave(slave); // throws exception on timeout - slaveList.add(slave); - } - } - - /** - * Called by the master harness to set the log stream for - * all slaves. Used to allow log output to be discarded - * for passing tests. - */ - static void setLogStreams(PrintStream stream) { - for (int i = 0; i < slaveList.size(); i++) { - SlaveData slave = (SlaveData) slaveList.get(i); - slave.pipe.setStream(stream); - } - } - - /** - * Connect to the given slave harness. Repeatedly attempt to open a socket - * to the slave log port. When successful, examine the value of the - * test property com.sun.jini.qa.harness.slavepipe. If defined and false, - * write a 0 to the socket. Otherwise, write a 1 to the socket. The slave - * reads this value to determine whether to pipe output through this socket. - * In either case, establish a pipe to pass input from the socket to the - * logger. - * - * @param slave the <code>SlaveData</code> corresponding to the slave - * @throws TestException if slave host is unknown or on timeout - */ - private static void connectToSlave(SlaveData slave) throws TestException { - try { - slave.addr = InetAddress.getByName(slave.name); - } catch (UnknownHostException e) { - throw new TestException("Unexpected exception", e); - } - for (int i = 0; i < 120; i++) { // try for 20 min at 10 sec intervals - try { - slave.logSocket = new Socket(slave.addr, LOG_PORT); - OutputStream os = slave.logSocket.getOutputStream(); - boolean doPipe = - QAConfig.getConfig().getBooleanConfigVal( - "com.sun.jini.qa.harness.slavepipe", - true); - os.write((doPipe? 1 : 0)); - os.flush(); - slave.pipe = new Pipe("slave-" + slave.name, - slave.logSocket.getInputStream(), - System.out, - null, - null); - slave.pipe.start(); - return; - } catch (ConnectException ignore) { - } catch (IOException e) { - throw new TestException("IOException connecting to " - + slave.name, - e); - } - try { - Thread.sleep(10000); - } catch (InterruptedException ignore) { - } - } - throw new TestException("Timeout connecting to slave " + slave.name); - } - - /** - * Sends a <code>HarnessRequest</code> object to a slave harness. - * A socket to the given slave's request port is opened and - * the request object is written to it. Then a read is hung - * on the socket. The object read from the socket is returned - * by this method; null is returned if the read throws an - * EOFException. In either case the socket is closed. - * - * @param slave the <code>SlaveData</code> describing the slave - * @param request the request to send - * @returns the reply object send by the slave, or null if the - * slave sent no reply - * @throws TestException on a connection or communication failure - */ - private static Object sendHarnessRequest(SlaveData slave, - HarnessRequest request) - throws TestException - { - logger.log(Level.FINER, - "Sending request to " + slave.name + ": " + request); - Socket s = null; - try { - s = new Socket(slave.addr, REQUEST_PORT); - ObjectOutputStream oos = - new ObjectOutputStream(s.getOutputStream()); - oos.writeObject(request); - oos.flush(); - } catch (Exception e) { - // fatal, so don't worry about closing sockets/streams - throw new TestException("Unexpected exception sending " - + "request to slave " - + slave.name, - e); - } - ObjectInputStream ois = null; - try { - ois = new ObjectInputStream(s.getInputStream()); - Object o = ois.readObject(); - return o; - } catch (EOFException e) { - return null; - } catch (Exception e) { - throw new TestException("Unexpected exception receiving " - + "response from slave " - + slave.name, e); - } finally { - try { - ois.close(); - s.close(); // redundant, I think - } catch (Exception ignore) { - } - } - } - - /** - * Broadcast the given request to all participating slaves. - * - * @param request the request to broadcast - * @throws TestException on a connection or communication failure - */ - static void broadcastRequest(HarnessRequest request) - throws TestException - { - for (int i = 0; i < slaveList.size(); i++) { - SlaveData slave = (SlaveData) slaveList.get(i); - sendHarnessRequest(slave, request); - } - } - - /** - * Construct the slave harness and its associated default config object. - * Exits the vm if the arg list is empty or an exception occurs while - * constructing the config object. - * - * @param args the command line args - */ - SlaveHarness(String[] args) { - if (args.length < 1) { - logger.log(Level.SEVERE, "Missing arguments"); - System.exit(1); - } - try { - config = new QAConfig(args); - } catch (Exception e) { - e.printStackTrace(); - logger.log(Level.SEVERE, - "Unexpected exception constructing config", - e); - System.exit(1); - } - // set these system properties so they will override any install - // properties included in a config sent by the master - System.setProperty("com.sun.jini.qa.home", config.getKitHomeDir()); - System.setProperty("com.sun.jini.jsk.home", config.getJSKHomeDir()); - System.setProperty("com.sun.jini.jsk.port", - config.getStringConfigVal("com.sun.jini.jsk.port", - null)); - System.setProperty("com.sun.jini.qa.port", - config.getStringConfigVal("com.sun.jini.qa.port", - null)); - System.setProperty( - "com.sun.jini.qa.harness.runjiniserver", - config.getStringConfigVal("com.sun.jini.qa.harness.runjiniserver", - null)); - System.setProperty( - "com.sun.jini.qa.harness.runkitserver", - config.getStringConfigVal("com.sun.jini.qa.harness.runkitserver", - null)); - boolean genHtml = config.getBooleanConfigVal( - "com.sun.jini.qa.harness.generateHtml", false); - if (genHtml) { - try { - HtmlReport htmlReport = new HtmlReport(config, null); - htmlReport.generate(); - } catch (Exception e) { - logger.log(Level.SEVERE, "Exception trying to generate index.html", e); - } - } - } - - /** - * Start the request handler thread and wait for a connection to the - * logging port. When the log connection is made, read a byte from the - * socket. If the value of the byte is non-zero, redirect System.out - * and System.err and logger output to the sockets output stream. - * Start the keep-alive thread which will detect the death of the master. - * A System.exit is done if any exceptions occur. - */ - void handleRequests() { - new Thread(new RequestHandler(), "RequestThread").start(); - try { - ServerSocket socket = new ServerSocket(LOG_PORT); - Timeout.TimeoutHandler handler = - new Timeout.ServerSocketTimeoutHandler(socket); - Timeout timeout = new Timeout(handler, 20 * 60 * 1000); // 20 min - timeout.start(); - Socket logSocket = socket.accept(); - timeout.cancel(); - InputStream is = logSocket.getInputStream(); - int pipeFlag = is.read(); - PrintStream ps = new PrintStream(logSocket.getOutputStream()); - if (pipeFlag !=0) { - System.setOut(ps); - System.setErr(ps); - reconfigureLogger(); - } - InetAddress masterAddress = logSocket.getInetAddress(); - new Thread(new Keepalive(masterAddress), "KeepAlive").start(); - } catch (Exception e) { - logger.log(Level.SEVERE, "Unexpected exception", e); - System.exit(1); - } - } - - /** - * Replace the <code>ReportHander</code> bound to the logger with a new - * instance of <code>ReportHandler</code>. This is necessary when output - * is redirected because the handler caches the value of System.err/out. - */ - private void reconfigureLogger() { - Handler[] handlers = logger.getHandlers(); - for (int i = 0; i < handlers.length; i++) { - if (handlers[i] instanceof ReportHandler) { - logger.removeHandler(handlers[i]); - } - } - logger.addHandler(new ReportHandler()); - } - - /** - * A thread which opens a socket to the master harness keep-alive - * port and hangs a read on the socket. If an EOF occurs, or any - * IOException is thrown, a System.exit is done. - */ - private class Keepalive implements Runnable { - - InetAddress addr; - - public Keepalive(InetAddress addr) { - this.addr = addr; - } - - public void run() { - try { - Socket s = new Socket(addr, MasterHarness.KEEPALIVE_PORT); - InputStream is = s.getInputStream(); - while (true) { - if (is.read() == -1) { - //best effort teardown in case master died. Do this - //because the System.exit doesn't seem to cleanup - try { - callLocalSlaveHandler(new TeardownRequest()); - } catch (Exception ignore) { - } - System.exit(1); - } - } - } catch (IOException e) { - e.printStackTrace(); - System.exit(1); - } - } - } - - /** - * A thread which processes slave <code>HarnessRequest</code> objects. - * A socket is accepted, the request object read from the socket - * input stream, and the requests <code>doHarnessRequest</code> method - * is called. On return, the request socket is closed; no return object - * is written to the socket. Any exception thrown will result in a - * System.exit. - */ - private class RequestHandler implements Runnable { - - ServerSocket socket; - Socket requestSocket; - Class policyClass = null; - - public void run() { - if (policyClass == null) { - try { - policyClass = - Class.forName("com.sun.jini.qa.harness.MergedPolicyProvider"); - if (policyClass.getClassLoader().getParent() != null) { - logger.log(Level.SEVERE, - "MergedPolicyProvider must be " - + "installed in an extensions directory"); - System.exit(1); - } - } catch (Exception e) { - logger.log(Level.SEVERE, - "failed to find MergedPolicyProvider"); - System.exit(1); - } - } - try { - socket = new ServerSocket(REQUEST_PORT); - while (true) { - requestSocket = socket.accept(); - ObjectInputStream ois = - new ObjectInputStream(requestSocket.getInputStream()); - HarnessRequest request = (HarnessRequest) ois.readObject(); - logger.log(Level.FINER, - "Received harness request: " + request); - request.doHarnessRequest(SlaveHarness.this); - ois.close(); - requestSocket.close(); - } - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - } - } - - /** - * Start the slave request handler VM. This is a callback to be used - * by a request handler. The given config object is used to construct - * the command line and is passed to the handler vm as a serialized - * object written to the processes System.in stream. Note that the - * given config is NOT the config associated with the slave harness, - * but is the serialized copy sent by the master harness in the - * request message. This method does not return until the request - * handlers request port is ready to accept requests. - * - * @param config the config object associated with the test - */ - void startSlaveTest(QAConfig masterConfig) { - if (getHarnessJar() != null) { - masterConfig.setDynamicParameter("harnessJar", getHarnessJar()); - } - if (config.getStringConfigVal("testJar", null) != null) { - masterConfig.setDynamicParameter("testJar", config.getStringConfigVal("testJar", null)); - } - masterConfig.buildSearchList(config.getStringConfigVal("searchPath", "")); - // configuration used to be reread by QAConfig.readObject, but jar references - // need to be fixed up before loading the configuration, and the local - // testJar param is not available to readObject - try { - masterConfig.loadTestConfiguration(); - } catch (TestException e) { - e.printStackTrace(); - } - try { - /* - * the following should theoretically be done with overrides set, - * since the qa kit may not be default and the TD for the test - * could be different. Ignoring this for now, due to an expectation - * that ultimately all qa kits must be the same on participants - * to avoid QAConfig versioning problems - */ - TestDescription td = masterConfig.getTestDescription(); - /* - * The getSystemProps method returns a collection of properties - * which override the default values. These properties will - * typically be com.sun.jini.jsk.home, etc. In many cases - * this collection will be empty. - */ - Properties overrideProperties = new Properties(); - String overrideProp = - config.getStringConfigVal("com.sun.jini.qa.harness.slaveOverrides", - null); - if (overrideProp != null) { - String[] overrides = config.parseString(overrideProp, ", "); // null OK - for (int i = 0; i < overrides.length; i++) { - String key = overrides[i]; - String value = config.getStringConfigVal(key, null); - if (value != null) { - masterConfig.setDynamicParameter(key, value); - overrideProperties.setProperty(key, value); - } - } - } - masterConfig.setHostNameToken(); -//XXXXXXX NEED TO SET NEW VALUES FOR testJar AND harnessJar !!!!!! - String[] cmdArray = -// td.getCommandLine(masterConfig.getSystemProps("slave")); - td.getCommandLine(overrideProperties); - if (logger.isLoggable(Level.FINE)) { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < cmdArray.length; i++) { - if (cmdArray[i].indexOf(' ') >= 0) { - sb.append("'").append(cmdArray[i]).append("' "); - } else { - sb.append(cmdArray[i]).append(" "); - } - } - logger.log(Level.FINE, - "Starting slave request handler " - + "in separate process with command:"); - logger.log(Level.FINE, sb.toString()); - } - File workingDir = td.getWorkingDir(); - Process proc = - Runtime.getRuntime().exec(cmdArray, null, workingDir); - ObjectOutputStream os = - new ObjectOutputStream(proc.getOutputStream()); - os.writeObject(masterConfig); - os.flush(); - bindOutput(proc); - if (! waitForSlaveTest()) { - //XXX should I throw an exception here? - logger.log(Level.SEVERE, - "No response from slave request handler"); - } - // an exception here seems pretty fatal, so log it and exit - // Delay to allow output to drain??? - } catch (Throwable e) { - logger.log(Level.INFO, "Unexpected exception", e); - System.exit(1); - } - } - - // return null if not found - support running from classes directory - private String getHarnessJar() { - String classpath = System.getProperty("java.class.path"); - StringTokenizer tok = - new StringTokenizer(classpath, File.pathSeparator); - while (tok.hasMoreTokens()) { - String path = tok.nextToken(); - JarFile jarFile; - try { - jarFile = new JarFile(path); - } catch (IOException e) { - logger.log(Level.FINEST, "failed to open jar file " + path, e); - continue; - } - if (jarFile.getEntry("com/sun/jini/qa/harness/QARunner.class") - != null) - { - return path; - } - } - return null; - } - - /** - * Repeatedly attempt to send ping requests to the test request handler - * running on this host. This method returns when a ping is sent - * successfully, or after a ten-second timeout expires. - * - * @return true if a ping was went successfully, false if all pings failed - */ - private boolean waitForSlaveTest() { - for (int i = 0; i < 20; i++) { - try { - callLocalSlaveHandler(new PingRequest()); - return true; - } catch (Exception ignore) { - } - try { - Thread.sleep(500); - } catch (InterruptedException ignore) { - } - } - return false; - } - - /** - * Call the test request handler on the local system. Any exception - * thrown is propogated to the caller - * - * @param request the message to send - * @throws Exception if the local host name cannot be resolved, or if - * the call to the request handler throws an exception. - */ - private void callLocalSlaveHandler(SlaveRequest request) throws Exception { - String name = InetAddress.getLocalHost().getHostName(); - SlaveTest.call(name, request); - } - - /** - * Bind the given process System.out/System.err streams to this - * objects logger. - * - * @param proc the process to bind - * @throws IOException if there is a problem getting the I/O streams - */ - private void bindOutput(Process proc) throws IOException { - new Pipe("slaveharness-out", - proc.getInputStream(), - System.out, - null, - null).start(); - new Pipe("slaveharness-err", - proc.getErrorStream(), - System.out, - null, - null).start(); - } -} +/* + * 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 com.sun.jini.qa.harness; + +import java.io.File; +import java.io.EOFException; +import java.io.InputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.jar.JarFile; + +/** + * A 'harness' which accepts work requests from a master over + * a socket. + * <p> + * Generation of the MasterTest and SlaveTest command lines: + * <p> + * There are 4 VMs which participate in the distributed test process: + * <ul> + * <li>the master harness + * <li>the slave harness + * <li>the master test + * <li>the slave test request handler + * <ul> + * All 4 of these VMs maintain an instance of QAConfig. Because the + * VMs are running on different systems, potentially with different + * versions of the jdk/jsk kits, the values which define installation + * directories for the master/slave tests are defined as system properties + * on the test VMs invoked by the master/slave harnesses. + * <p> + * When the MasterHarness and SlaveHarness classes are instantiated, + * they construct an instance of QAConfig. The constructor of QAConfig + * verifies the existance of the following system properties: + * <ul> + * <li>com.sun.jini.jsk.home + * <li>com.sun.jini.qa.home + * </ul> + * which are typically provided by the user config file. These values serve as + * the default values to apply to the master/slave test VMs. When the + * <code>MasterHarness</code> creates the master test VM, it obtains the command + * line from the <code>TestDescription</code>. When generating the command + * line, the TD temporarily sets override properties generated from the QAConfig + * getSystemProps("master") method. Any master overrides supplied (indirectly) + * through the environment file (provided by the -env command line option) will + * be used to build the command line. Typically, one or more of the installation + * system properties listed above will be replaced if the test is to use a + * non-default kit. The master harness always provides property definitions for + * all of these properties so that policy files and ConfigurationFile entries + * have access to these definitions. The MasterHarness passes a serialized copy + * of its QAConfig to the master test over its System.in stream; it is not + * necessary for the master test to construct a QAConfig from scratch. Because + * system property definitions override the user configuration file values when + * QAConfig searches for a property, the values set on the command line + * by the harness will take precedence. + * <p> + * When the <code>MasterHarness</code> submits a request to the slave + * harness to create a <code>SlaveTest</code>, it also passes + * a serialized copy of its QAConfig. The <code>SlaveHarness</code> + * generates a command line, this time using overrides resulting from + * calling the QAConfig getSystemProps("slave") method. The information + * required to generate the slave overrides are carried in the serialized + * QAConfig object, so the <code>MasterHarness</code> controls the + * environment in which the <code>SlaveTest</code> runs. + */ +class SlaveHarness { + + /** The port used for piping System.out/System.err to the master */ + final static int LOG_PORT=10001; + + /** The port used to accept request messages */ + final static int REQUEST_PORT=10002; + + /** the logger */ + private static Logger logger = + Logger.getLogger("com.sun.jini.qa.harness"); + + /** + * The config object used directly by this class. It's primary + * purpose is to establish default values for installation + * properties such as com.sun.jini.jsk.home. The config used + * by the request handler vm is provided by the master harness + * and may provide overrides for the installation properties. + */ + QAConfig config; + + /** The list of slaves participating in this test run */ + private static ArrayList slaveList = new ArrayList(); + + /** Data structure holding slave info */ + private static class SlaveData { + String name; + InetAddress addr; + Socket logSocket; + Pipe pipe; + }; + + /** + * Called by the master harness to connect to all slave harnesses + * to be used by the test. If the test property + * com.sun.jini.qa.harness.testhosts specifies any slave harnesses, + * create the pipes to capture any output from the slave harnesses + * into the test log. Failure to connect to any slave within 5 + * minutes (XXX parameterize) results in a TestException being + * thrown, aborting the test run. + * + * @throws TestException on connection timeout + */ + static void connect() throws TestException { + ArrayList hostList = QAConfig.getConfig().getHostList(); + if (hostList.size() < 2) { + return; + } + // connect to all slaves and build slaveList + for (int i = 1; i < hostList.size(); i++) { + String host = (String) hostList.get(i); + SlaveData slave = new SlaveData(); + slave.name = host; + connectToSlave(slave); // throws exception on timeout + slaveList.add(slave); + } + } + + /** + * Called by the master harness to set the log stream for + * all slaves. Used to allow log output to be discarded + * for passing tests. + */ + static void setLogStreams(PrintStream stream) { + for (int i = 0; i < slaveList.size(); i++) { + SlaveData slave = (SlaveData) slaveList.get(i); + slave.pipe.setStream(stream); + } + } + + /** + * Connect to the given slave harness. Repeatedly attempt to open a socket + * to the slave log port. When successful, examine the value of the + * test property com.sun.jini.qa.harness.slavepipe. If defined and false, + * write a 0 to the socket. Otherwise, write a 1 to the socket. The slave + * reads this value to determine whether to pipe output through this socket. + * In either case, establish a pipe to pass input from the socket to the + * logger. + * + * @param slave the <code>SlaveData</code> corresponding to the slave + * @throws TestException if slave host is unknown or on timeout + */ + private static void connectToSlave(SlaveData slave) throws TestException { + try { + slave.addr = InetAddress.getByName(slave.name); + } catch (UnknownHostException e) { + throw new TestException("Unexpected exception", e); + } + for (int i = 0; i < 120; i++) { // try for 20 min at 10 sec intervals + try { + slave.logSocket = new Socket(slave.addr, LOG_PORT); + OutputStream os = slave.logSocket.getOutputStream(); + boolean doPipe = + QAConfig.getConfig().getBooleanConfigVal( + "com.sun.jini.qa.harness.slavepipe", + true); + os.write((doPipe? 1 : 0)); + os.flush(); + slave.pipe = new Pipe("slave-" + slave.name, + slave.logSocket.getInputStream(), + System.out, + null, + null); + slave.pipe.start(); + return; + } catch (ConnectException ignore) { + } catch (IOException e) { + throw new TestException("IOException connecting to " + + slave.name, + e); + } + try { + Thread.sleep(10000); + } catch (InterruptedException ignore) { + Thread.currentThread().interrupt(); + } + } + throw new TestException("Timeout connecting to slave " + slave.name); + } + + /** + * Sends a <code>HarnessRequest</code> object to a slave harness. + * A socket to the given slave's request port is opened and + * the request object is written to it. Then a read is hung + * on the socket. The object read from the socket is returned + * by this method; null is returned if the read throws an + * EOFException. In either case the socket is closed. + * + * @param slave the <code>SlaveData</code> describing the slave + * @param request the request to send + * @returns the reply object send by the slave, or null if the + * slave sent no reply + * @throws TestException on a connection or communication failure + */ + private static Object sendHarnessRequest(SlaveData slave, + HarnessRequest request) + throws TestException + { + logger.log(Level.FINER, + "Sending request to " + slave.name + ": " + request); + Socket s = null; + try { + s = new Socket(slave.addr, REQUEST_PORT); + ObjectOutputStream oos = + new ObjectOutputStream(s.getOutputStream()); + oos.writeObject(request); + oos.flush(); + } catch (Exception e) { + // fatal, so don't worry about closing sockets/streams + throw new TestException("Unexpected exception sending " + + "request to slave " + + slave.name, + e); + } + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(s.getInputStream()); + Object o = ois.readObject(); + return o; + } catch (EOFException e) { + return null; + } catch (Exception e) { + throw new TestException("Unexpected exception receiving " + + "response from slave " + + slave.name, e); + } finally { + try { + ois.close(); + s.close(); // redundant, I think + } catch (Exception ignore) { + } + } + } + + /** + * Broadcast the given request to all participating slaves. + * + * @param request the request to broadcast + * @throws TestException on a connection or communication failure + */ + static void broadcastRequest(HarnessRequest request) + throws TestException + { + for (int i = 0; i < slaveList.size(); i++) { + SlaveData slave = (SlaveData) slaveList.get(i); + sendHarnessRequest(slave, request); + } + } + + /** + * Construct the slave harness and its associated default config object. + * Exits the vm if the arg list is empty or an exception occurs while + * constructing the config object. + * + * @param args the command line args + */ + SlaveHarness(String[] args) { + if (args.length < 1) { + logger.log(Level.SEVERE, "Missing arguments"); + System.exit(1); + } + try { + config = new QAConfig(args); + } catch (Exception e) { + e.printStackTrace(); + logger.log(Level.SEVERE, + "Unexpected exception constructing config", + e); + System.exit(1); + } + // set these system properties so they will override any install + // properties included in a config sent by the master + System.setProperty("com.sun.jini.qa.home", config.getKitHomeDir()); + System.setProperty("com.sun.jini.jsk.home", config.getJSKHomeDir()); + System.setProperty("com.sun.jini.jsk.port", + config.getStringConfigVal("com.sun.jini.jsk.port", + null)); + System.setProperty("com.sun.jini.qa.port", + config.getStringConfigVal("com.sun.jini.qa.port", + null)); + System.setProperty( + "com.sun.jini.qa.harness.runjiniserver", + config.getStringConfigVal("com.sun.jini.qa.harness.runjiniserver", + null)); + System.setProperty( + "com.sun.jini.qa.harness.runkitserver", + config.getStringConfigVal("com.sun.jini.qa.harness.runkitserver", + null)); + boolean genHtml = config.getBooleanConfigVal( + "com.sun.jini.qa.harness.generateHtml", false); + if (genHtml) { + try { + HtmlReport htmlReport = new HtmlReport(config, null); + htmlReport.generate(); + } catch (Exception e) { + logger.log(Level.SEVERE, "Exception trying to generate index.html", e); + } + } + } + + /** + * Start the request handler thread and wait for a connection to the + * logging port. When the log connection is made, read a byte from the + * socket. If the value of the byte is non-zero, redirect System.out + * and System.err and logger output to the sockets output stream. + * Start the keep-alive thread which will detect the death of the master. + * A System.exit is done if any exceptions occur. + */ + void handleRequests() { + new Thread(new RequestHandler(), "RequestThread").start(); + try { + ServerSocket socket = new ServerSocket(LOG_PORT); + Timeout.TimeoutHandler handler = + new Timeout.ServerSocketTimeoutHandler(socket); + Timeout timeout = new Timeout(handler, 20 * 60 * 1000); // 20 min + timeout.start(); + Socket logSocket = socket.accept(); + timeout.cancel(); + InputStream is = logSocket.getInputStream(); + int pipeFlag = is.read(); + PrintStream ps = new PrintStream(logSocket.getOutputStream()); + if (pipeFlag !=0) { + System.setOut(ps); + System.setErr(ps); + reconfigureLogger(); + } + InetAddress masterAddress = logSocket.getInetAddress(); + new Thread(new Keepalive(masterAddress), "KeepAlive").start(); + } catch (Exception e) { + logger.log(Level.SEVERE, "Unexpected exception", e); + System.exit(1); + } + } + + /** + * Replace the <code>ReportHander</code> bound to the logger with a new + * instance of <code>ReportHandler</code>. This is necessary when output + * is redirected because the handler caches the value of System.err/out. + */ + private void reconfigureLogger() { + Handler[] handlers = logger.getHandlers(); + for (int i = 0; i < handlers.length; i++) { + if (handlers[i] instanceof ReportHandler) { + logger.removeHandler(handlers[i]); + } + } + logger.addHandler(new ReportHandler()); + } + + /** + * A thread which opens a socket to the master harness keep-alive + * port and hangs a read on the socket. If an EOF occurs, or any + * IOException is thrown, a System.exit is done. + */ + private class Keepalive implements Runnable { + + InetAddress addr; + + public Keepalive(InetAddress addr) { + this.addr = addr; + } + + public void run() { + try { + Socket s = new Socket(addr, MasterHarness.KEEPALIVE_PORT); + InputStream is = s.getInputStream(); + while (true) { + if (is.read() == -1) { + //best effort teardown in case master died. Do this + //because the System.exit doesn't seem to cleanup + try { + callLocalSlaveHandler(new TeardownRequest()); + } catch (Exception ignore) { + } + System.exit(1); + } + } + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + } + } + + /** + * A thread which processes slave <code>HarnessRequest</code> objects. + * A socket is accepted, the request object read from the socket + * input stream, and the requests <code>doHarnessRequest</code> method + * is called. On return, the request socket is closed; no return object + * is written to the socket. Any exception thrown will result in a + * System.exit. + */ + private class RequestHandler implements Runnable { + + ServerSocket socket; + Socket requestSocket; + Class policyClass = null; + + public void run() { + if (policyClass == null) { + try { + policyClass = + Class.forName("com.sun.jini.qa.harness.MergedPolicyProvider"); + if (policyClass.getClassLoader().getParent() != null) { + logger.log(Level.SEVERE, + "MergedPolicyProvider must be " + + "installed in an extensions directory"); + System.exit(1); + } + } catch (Exception e) { + logger.log(Level.SEVERE, + "failed to find MergedPolicyProvider"); + System.exit(1); + } + } + try { + socket = new ServerSocket(REQUEST_PORT); + while (true) { + requestSocket = socket.accept(); + ObjectInputStream ois = + new ObjectInputStream(requestSocket.getInputStream()); + HarnessRequest request = (HarnessRequest) ois.readObject(); + logger.log(Level.FINER, + "Received harness request: " + request); + request.doHarnessRequest(SlaveHarness.this); + ois.close(); + requestSocket.close(); + } + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } + } + + /** + * Start the slave request handler VM. This is a callback to be used + * by a request handler. The given config object is used to construct + * the command line and is passed to the handler vm as a serialized + * object written to the processes System.in stream. Note that the + * given config is NOT the config associated with the slave harness, + * but is the serialized copy sent by the master harness in the + * request message. This method does not return until the request + * handlers request port is ready to accept requests. + * + * @param config the config object associated with the test + */ + void startSlaveTest(QAConfig masterConfig) { + if (getHarnessJar() != null) { + masterConfig.setDynamicParameter("harnessJar", getHarnessJar()); + } + if (config.getStringConfigVal("testJar", null) != null) { + masterConfig.setDynamicParameter("testJar", config.getStringConfigVal("testJar", null)); + } + masterConfig.buildSearchList(config.getStringConfigVal("searchPath", "")); + // configuration used to be reread by QAConfig.readObject, but jar references + // need to be fixed up before loading the configuration, and the local + // testJar param is not available to readObject + try { + masterConfig.loadTestConfiguration(); + } catch (TestException e) { + e.printStackTrace(); + } + try { + /* + * the following should theoretically be done with overrides set, + * since the qa kit may not be default and the TD for the test + * could be different. Ignoring this for now, due to an expectation + * that ultimately all qa kits must be the same on participants + * to avoid QAConfig versioning problems + */ + TestDescription td = masterConfig.getTestDescription(); + /* + * The getSystemProps method returns a collection of properties + * which override the default values. These properties will + * typically be com.sun.jini.jsk.home, etc. In many cases + * this collection will be empty. + */ + Properties overrideProperties = new Properties(); + String overrideProp = + config.getStringConfigVal("com.sun.jini.qa.harness.slaveOverrides", + null); + if (overrideProp != null) { + String[] overrides = config.parseString(overrideProp, ", "); // null OK + for (int i = 0; i < overrides.length; i++) { + String key = overrides[i]; + String value = config.getStringConfigVal(key, null); + if (value != null) { + masterConfig.setDynamicParameter(key, value); + overrideProperties.setProperty(key, value); + } + } + } + masterConfig.setHostNameToken(); +//XXXXXXX NEED TO SET NEW VALUES FOR testJar AND harnessJar !!!!!! + String[] cmdArray = +// td.getCommandLine(masterConfig.getSystemProps("slave")); + td.getCommandLine(overrideProperties); + if (logger.isLoggable(Level.FINE)) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < cmdArray.length; i++) { + if (cmdArray[i].indexOf(' ') >= 0) { + sb.append("'").append(cmdArray[i]).append("' "); + } else { + sb.append(cmdArray[i]).append(" "); + } + } + logger.log(Level.FINE, + "Starting slave request handler " + + "in separate process with command:"); + logger.log(Level.FINE, sb.toString()); + } + File workingDir = td.getWorkingDir(); + Process proc = + Runtime.getRuntime().exec(cmdArray, null, workingDir); + ObjectOutputStream os = + new ObjectOutputStream(proc.getOutputStream()); + os.writeObject(masterConfig); + os.flush(); + bindOutput(proc); + if (! waitForSlaveTest()) { + //XXX should I throw an exception here? + logger.log(Level.SEVERE, + "No response from slave request handler"); + } + // an exception here seems pretty fatal, so log it and exit + // Delay to allow output to drain??? + } catch (Throwable e) { + logger.log(Level.INFO, "Unexpected exception", e); + System.exit(1); + } + } + + // return null if not found - support running from classes directory + private String getHarnessJar() { + String classpath = System.getProperty("java.class.path"); + StringTokenizer tok = + new StringTokenizer(classpath, File.pathSeparator); + while (tok.hasMoreTokens()) { + String path = tok.nextToken(); + JarFile jarFile; + try { + jarFile = new JarFile(path); + } catch (IOException e) { + logger.log(Level.FINEST, "failed to open jar file " + path, e); + continue; + } + if (jarFile.getEntry("com/sun/jini/qa/harness/QARunner.class") + != null) + { + return path; + } + } + return null; + } + + /** + * Repeatedly attempt to send ping requests to the test request handler + * running on this host. This method returns when a ping is sent + * successfully, or after a ten-second timeout expires. + * + * @return true if a ping was went successfully, false if all pings failed + */ + private boolean waitForSlaveTest() { + for (int i = 0; i < 20; i++) { + try { + callLocalSlaveHandler(new PingRequest()); + return true; + } catch (Exception ignore) { + } + try { + Thread.sleep(500); + } catch (InterruptedException ignore) { + Thread.currentThread().interrupt(); + } + } + return false; + } + + /** + * Call the test request handler on the local system. Any exception + * thrown is propogated to the caller + * + * @param request the message to send + * @throws Exception if the local host name cannot be resolved, or if + * the call to the request handler throws an exception. + */ + private void callLocalSlaveHandler(SlaveRequest request) throws Exception { + String name = InetAddress.getLocalHost().getHostName(); + SlaveTest.call(name, request); + } + + /** + * Bind the given process System.out/System.err streams to this + * objects logger. + * + * @param proc the process to bind + * @throws IOException if there is a problem getting the I/O streams + */ + private void bindOutput(Process proc) throws IOException { + new Pipe("slaveharness-out", + proc.getInputStream(), + System.out, + null, + null).start(); + new Pipe("slaveharness-err", + proc.getErrorStream(), + System.out, + null, + null).start(); + } +}
Modified: river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/SlaveTest.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/SlaveTest.java?rev=1634322&r1=1634321&r2=1634322&view=diff ============================================================================== --- river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/SlaveTest.java (original) +++ river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/SlaveTest.java Sun Oct 26 13:17:28 2014 @@ -1,381 +1,391 @@ -/* - * 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 com.sun.jini.qa.harness; - -import java.io.EOFException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.PrintStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.logging.Logger; -import java.util.logging.Level; - -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; -import javax.security.auth.Subject; - -import net.jini.config.Configuration; - -/** - * The slave side of a distributed test. This class provides the main - * method for in its own VM, provides static utility method for - * sending messages to a <code>SlaveTest</code> instance, and implement - * a message handler to receive and execute <code>SlaveRequest</code> - * messages. - */ -public class SlaveTest { - - /** the port for receiving <code>SlaveRequest</code> messages */ - private final static int REQUEST_PORT=10003; - - /** the logger */ - private static Logger logger = - Logger.getLogger("com.sun.jini.qa.harness"); - - /** the original <code>System.err</code> stream for this VM */ - private static PrintStream origErr; - - /** the test properties, unmarshalled from <code>System.in</code> */ - static QAConfig config; // MasterTest access hack - - /** a flag indicating that the VM should <code>System.exit</code> */ - private static boolean doExit = false; - - /** the admin manager */ - static AdminManager manager; // MasterTest access hack - - /** persistent storage across calls */ - private static HashMap storageMap = new HashMap(); - - /** - * Utility method to send a <code>SlaveRequest</code> message - * to a SlaveTest instance. - * - * @param name the name of the slave host - * @param request the request to send - * @return the object by the <code>SlaveRequest.doSlaveRequest</code> method - * @throws TestException if the call fails - */ - public static Object call(String name, SlaveRequest request) - throws TestException - { - Socket s = null; - logger.log(Level.FINE, - "Sending request to slave test on " + name + ": " + request); - try { - s = new Socket(name, REQUEST_PORT); - ObjectOutputStream oos = - new ObjectOutputStream(s.getOutputStream()); - oos.writeObject(request); - oos.flush(); - // oos.close(); - } catch (Exception e) { - // fatal, so don't worry about closing sockets/streams - throw new TestException("Unexpected exception sending " - + "request to slave " - + name, e); - } - ObjectInputStream ois = null; - try { - ois = new ObjectInputStream(s.getInputStream()); - Object o = ois.readObject(); - return o; - } catch (EOFException e) { - return null; - } catch (Exception e) { - throw new TestException("Unexpected exception receiving " - + "response from slave " - + name); - } finally { - try { - ois.close(); - s.close(); // redundant, I think - } catch (Exception ignore) { - } - } - } - - /** - * Utility method to 'broadcast' a <code>SlaveRequest</code> message - * to all participating <code>SlaveTest</code>s. The request - * is sent to each participant in sequence. Failures are logged, - * but otherwise ignored. - * - * @param request the <code>SlaveRequest</code> to send - */ - public static void broadcast(SlaveRequest request) { - ArrayList hostList = QAConfig.getConfig().getHostList(); - if (hostList.size() < 2) { - return; - } - for (int i = 1; i < hostList.size(); i++) { - String name = (String) hostList.get(i); - try { - call(name, request); - } catch (Exception e) { - logger.log(Level.INFO, "Call to slave threw exception", e); - } - } - } - - /** - * Wait for all slaves to die. All participating slave are - * pinged once per second until <code>timeout</code> seconds - * elapse. When all pings throw <code>Exception</code>, - * the slaves are assumed to be dead and this method returns. - * - * @param timeout maximum time in seconds to way for slave death. If - * zero or negative, this method returns immediately - */ - public static void waitForSlaveDeath(int timeout) { - ArrayList hostList = QAConfig.getConfig().getHostList(); - if (hostList.size() < 2) { - return; - } - ArrayList slaveList = new ArrayList(); - for (int i = 1; i < hostList.size(); i++) { - slaveList.add(hostList.get(i)); - } - PingRequest ping = new PingRequest(); - for (int i = 0; i < timeout; i++) { - for (int j = slaveList.size() - 1; j >= 0; j--) { - String slaveName = (String) slaveList.get(j); - try { - call(slaveName, ping); - } catch (Exception e) { - slaveList.remove(j); - } - } - if (slaveList.size() == 0) { - return; - } - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - } - } - } - - - /** - * The main method for the slave test VM. The <code>QAConfig</code> - * object is read from <code>System.in</code>, and <code>AdminManager</code> - * is instantiated, class servers are started, and the request - * handling loop is entered (optionally in the context of a user login). - * The <code>args</code> passed to the main method are not used; the - * args supplied to the master test are available in <code>QAConfig</code>. - * - * @param args the command line arguments (unused) - */ - public static void main(String[] args) { - origErr = System.err; - System.setErr(System.out); - if (System.getSecurityManager() == null) { - System.setSecurityManager(new java.rmi.RMISecurityManager()); - } - try { - ObjectInputStream ois = new ObjectInputStream(System.in); - config = (QAConfig) ois.readObject(); - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - // used to be handled by config.readObject, but this broke SlaveHarness - try { - config.loadTestConfiguration(); - } catch (TestException e) { - e.printStackTrace(); - } - manager = new AdminManager(config); - Configuration c = config.getConfiguration(); // the davis config - LoginContext context = null; - try { - context = (LoginContext) c.getEntry("test", - "loginContext", - LoginContext.class, - null); - if (context != null) { - logger.log(Level.FINEST, "got a login context"); - } - } catch (Throwable e) { - e.printStackTrace(); - System.exit(1); - } - Thread autotRequestThread = - new Thread(new AutotRequestHandler()); - // this property is supplied by the generator - String callAutot = - config.getStringConfigVal("com.sun.jini.qa.harness.callAutoT", - null); - if (callAutot != null) { - autotRequestThread.setDaemon(true); - autotRequestThread.start(); - config.enableTestHostCalls(true); - } - config.callTestHost(new InboundCallsEnabledRequest(true)); - config.callTestHost(new TestStatusRequest("Handling slave requests")); - if (context != null) { - handleRequestsWithLogin(context); //must call exit - } else { - handleRequests(); // must call exit - } - } - - /** - * Run the request loop in the context of the given - * <code>LoginContext</code>. - * - * @param context the <code>LoginContext</code> to use - */ - private static void handleRequestsWithLogin(LoginContext context) { - try { - context.login(); - } catch (Throwable e) { - e.printStackTrace(); - System.exit(1); - } - // doTest should always call exit, so this call never returns - Subject.doAsPrivileged(context.getSubject(), - new PrivilegedAction() { - public Object run() { - handleRequests(); - return null; - } - }, - null); - } - - /** - * The loop for accepting and processing <code>SlaveRequest</code> - * messages. Each message is sent over a separate socket connection - * in serialized form. After unmarshalling the request, the - * <code>doSlaveRequeset</code> method is called passing an instance - * of this class; the object returned by the method is written - * back to the caller and the connection closed. If the method - * throws an exception, the exception is written back instead. - * The loop (and VM) exits when a <code>SlaveRequest</code> is - * received which call <code>SlaveTest.exit()</code>. - */ - private static void handleRequests() { - try { - ServerSocket socket = new ServerSocket(REQUEST_PORT); - while (!doExit) { - Socket requestSocket = socket.accept(); - logger.log(Level.FINER, "Got a test slave request"); - ObjectInputStream ois = - new ObjectInputStream(requestSocket.getInputStream()); - SlaveRequest request = (SlaveRequest) ois.readObject(); - logger.log(Level.FINER, "Request is: " + request); - Object o = null; - try { - o = request.doSlaveRequest(new SlaveTest()); - } catch (Throwable e) { - logger.log(Level.SEVERE, "Unexpected Exception", e); - o = e; - } - ObjectOutputStream oos = - new ObjectOutputStream(requestSocket.getOutputStream()); - oos.writeObject(o); - oos.flush(); - oos.close(); - requestSocket.close();//redundant?? - } - socket.close(); - config.callTestHost(new TestStatusRequest("Advancing to next test")); - config.callTestHost(new InboundCallsEnabledRequest(false)); - System.exit(0); - } catch (Throwable e) { - logger.log(Level.SEVERE, "Unexpected exception", e); - System.exit(1); //??? - } - } - - /** - * Called by a <code>SlaveRequest</code> to cause the <code>SlaveTest</code> - * to exit. - */ - void exit() { - doExit = true; - } - - /** - * Accessor for <code>SlaveRequest</code> to obtain the config object. - * - * @return the <code>QAConfig</code> object - */ - public QAConfig getConfig() { - return config; - } - - /** - * Accessor for <code>SlaveRequest</code> to obtain the admin manager. - * - * @return the <code>AdminManager</code> - */ - public AdminManager getAdminManager() { - return manager; - } - - /** - * Accessor for the storage map which allow the test to - * maintain state across slave calls. - * - * @return the storage map - */ - public HashMap getStorageMap() { - return storageMap; - } - - private static class AutotRequestHandler implements Runnable { - - public void run() { - try { - ServerSocket socket = - new ServerSocket(InboundAutotRequest.PORT); - while (true) { - Socket requestSocket = socket.accept(); - logger.log(Level.FINER, "Got an external request"); - ObjectInputStream ois = - new ObjectInputStream(requestSocket.getInputStream()); - InboundAutotRequest request = - (InboundAutotRequest) ois.readObject(); - logger.log(Level.FINER, "Request is: " + request); - Object o = null; - try { - o = request.doRequest(config, manager); - } catch (Throwable e) { - logger.log(Level.SEVERE, "Unexpected Exception", e); - o = e; - } - ObjectOutputStream oos = - new ObjectOutputStream(requestSocket.getOutputStream()); - oos.writeObject(o); - oos.flush(); - oos.close(); - requestSocket.close();//redundant?? - } - } catch (Throwable e) { - logger.log(Level.SEVERE, "Unexpected exception", e); - } - } - } -} +/* + * 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 com.sun.jini.qa.harness; + +import java.io.EOFException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.logging.Logger; +import java.util.logging.Level; + +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.security.auth.Subject; + +import net.jini.config.Configuration; +import org.apache.river.api.security.CombinerSecurityManager; + +/** + * The slave side of a distributed test. This class provides the main + * method for in its own VM, provides static utility method for + * sending messages to a <code>SlaveTest</code> instance, and implement + * a message handler to receive and execute <code>SlaveRequest</code> + * messages. + */ +public class SlaveTest { + + /** the port for receiving <code>SlaveRequest</code> messages */ + private final static int REQUEST_PORT=10003; + + /** the logger */ + private static Logger logger = + Logger.getLogger("com.sun.jini.qa.harness"); + + /** the original <code>System.err</code> stream for this VM */ + private static PrintStream origErr; + + /** the test properties, unmarshalled from <code>System.in</code> */ + static QAConfig config; // MasterTest access hack + + /** a flag indicating that the VM should <code>System.exit</code> */ + private static boolean doExit = false; + + /** the admin manager */ + static AdminManager manager; // MasterTest access hack + + /** persistent storage across calls */ + private static HashMap storageMap = new HashMap(); + + /** + * Utility method to send a <code>SlaveRequest</code> message + * to a SlaveTest instance. + * + * @param name the name of the slave host + * @param request the request to send + * @return the object by the <code>SlaveRequest.doSlaveRequest</code> method + * @throws TestException if the call fails + */ + public static Object call(String name, SlaveRequest request) + throws TestException + { + Socket s = null; + logger.log(Level.FINE, + "Sending request to slave test on " + name + ": " + request); + try { + s = new Socket(name, REQUEST_PORT); + ObjectOutputStream oos = + new ObjectOutputStream(s.getOutputStream()); + oos.writeObject(request); + oos.flush(); + // oos.close(); + } catch (Exception e) { + // fatal, so don't worry about closing sockets/streams + throw new TestException("Unexpected exception sending " + + "request to slave " + + name, e); + } + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(s.getInputStream()); + Object o = ois.readObject(); + return o; + } catch (EOFException e) { + return null; + } catch (Exception e) { + throw new TestException("Unexpected exception receiving " + + "response from slave " + + name); + } finally { + try { + ois.close(); + s.close(); // redundant, I think + } catch (Exception ignore) { + } + } + } + + /** + * Utility method to 'broadcast' a <code>SlaveRequest</code> message + * to all participating <code>SlaveTest</code>s. The request + * is sent to each participant in sequence. Failures are logged, + * but otherwise ignored. + * + * @param request the <code>SlaveRequest</code> to send + */ + public static void broadcast(SlaveRequest request) { + ArrayList hostList = QAConfig.getConfig().getHostList(); + if (hostList.size() < 2) { + return; + } + for (int i = 1; i < hostList.size(); i++) { + String name = (String) hostList.get(i); + try { + call(name, request); + } catch (Exception e) { + logger.log(Level.INFO, "Call to slave threw exception", e); + } + } + } + + /** + * Wait for all slaves to die. All participating slave are + * pinged once per second until <code>timeout</code> seconds + * elapse. When all pings throw <code>Exception</code>, + * the slaves are assumed to be dead and this method returns. + * + * @param timeout maximum time in seconds to way for slave death. If + * zero or negative, this method returns immediately + */ + public static void waitForSlaveDeath(int timeout) { + ArrayList hostList = QAConfig.getConfig().getHostList(); + if (hostList.size() < 2) { + return; + } + ArrayList slaveList = new ArrayList(); + for (int i = 1; i < hostList.size(); i++) { + slaveList.add(hostList.get(i)); + } + PingRequest ping = new PingRequest(); + for (int i = 0; i < timeout; i++) { + for (int j = slaveList.size() - 1; j >= 0; j--) { + String slaveName = (String) slaveList.get(j); + try { + call(slaveName, ping); + } catch (Exception e) { + slaveList.remove(j); + } + } + if (slaveList.size() == 0) { + return; + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // restore + } + } + } + + + /** + * The main method for the slave test VM. The <code>QAConfig</code> + * object is read from <code>System.in</code>, and <code>AdminManager</code> + * is instantiated, class servers are started, and the request + * handling loop is entered (optionally in the context of a user login). + * The <code>args</code> passed to the main method are not used; the + * args supplied to the master test are available in <code>QAConfig</code>. + * + * @param args the command line arguments (unused) + */ + public static void main(String[] args) { + origErr = System.err; + System.setErr(System.out); + if (System.getSecurityManager() == null) { + System.setSecurityManager(new CombinerSecurityManager()); + } + try { + ObjectInputStream ois = new ObjectInputStream(System.in); + config = (QAConfig) ois.readObject(); + } catch (Exception e) { + e.fillInStackTrace(); + logger.log(Level.SEVERE, "Unexpected exception ", e); + System.exit(1); + } + // used to be handled by config.readObject, but this broke SlaveHarness + try { + config.loadTestConfiguration(); + } catch (TestException e) { + e.fillInStackTrace(); + logger.log(Level.SEVERE, "Unexpected exception ", e); + } + manager = new AdminManager(config); + Configuration c = config.getConfiguration(); // the davis config + LoginContext context = null; + try { + context = (LoginContext) c.getEntry("test", + "loginContext", + LoginContext.class, + null); + if (context != null) { + logger.log(Level.FINEST, "got a login context"); + } + } catch (Throwable e) { + e.fillInStackTrace(); + logger.log(Level.SEVERE, "Unexpected exception ", e); + System.exit(1); + } + Thread autotRequestThread = + new Thread(new AutotRequestHandler()); + // this property is supplied by the generator + String callAutot = + config.getStringConfigVal("com.sun.jini.qa.harness.callAutoT", + null); + if (callAutot != null) { + autotRequestThread.setDaemon(true); + autotRequestThread.start(); + config.enableTestHostCalls(true); + } + config.callTestHost(new InboundCallsEnabledRequest(true)); + config.callTestHost(new TestStatusRequest("Handling slave requests")); + if (context != null) { + handleRequestsWithLogin(context); //must call exit + } else { + handleRequests(); // must call exit + } + } + + /** + * Run the request loop in the context of the given + * <code>LoginContext</code>. + * + * @param context the <code>LoginContext</code> to use + */ + private static void handleRequestsWithLogin(LoginContext context) { + try { + context.login(); + } catch (Throwable e) { + e.fillInStackTrace(); + logger.log(Level.SEVERE, "Unexpected exception ", e); + System.exit(1); + } + // doTest should always call exit, so this call never returns + Subject.doAsPrivileged(context.getSubject(), + new PrivilegedAction() { + public Object run() { + handleRequests(); + return null; + } + }, + null); + } + + /** + * The loop for accepting and processing <code>SlaveRequest</code> + * messages. Each message is sent over a separate socket connection + * in serialized form. After unmarshalling the request, the + * <code>doSlaveRequeset</code> method is called passing an instance + * of this class; the object returned by the method is written + * back to the caller and the connection closed. If the method + * throws an exception, the exception is written back instead. + * The loop (and VM) exits when a <code>SlaveRequest</code> is + * received which call <code>SlaveTest.exit()</code>. + */ + private static void handleRequests() { + try { + ServerSocket socket = new ServerSocket(REQUEST_PORT); + while (!doExit) { + Socket requestSocket = socket.accept(); + logger.log(Level.FINER, "Got a test slave request"); + ObjectInputStream ois = + new ObjectInputStream(requestSocket.getInputStream()); + SlaveRequest request = (SlaveRequest) ois.readObject(); + logger.log(Level.FINER, "Request is: " + request); + Object o = null; + try { + o = request.doSlaveRequest(new SlaveTest()); + } catch (Throwable e) { + e.fillInStackTrace(); + logger.log(Level.SEVERE, "Unexpected Exception ", e); + o = e; + } + ObjectOutputStream oos = + new ObjectOutputStream(requestSocket.getOutputStream()); + oos.writeObject(o); + oos.flush(); + oos.close(); + requestSocket.close();//redundant?? + } + socket.close(); + config.callTestHost(new TestStatusRequest("Advancing to next test")); + config.callTestHost(new InboundCallsEnabledRequest(false)); + System.exit(0); + } catch (Throwable e) { + e.fillInStackTrace(); + logger.log(Level.SEVERE, "Unexpected exception", e); + System.exit(1); //??? + } + } + + /** + * Called by a <code>SlaveRequest</code> to cause the <code>SlaveTest</code> + * to exit. + */ + void exit() { + doExit = true; + } + + /** + * Accessor for <code>SlaveRequest</code> to obtain the config object. + * + * @return the <code>QAConfig</code> object + */ + public QAConfig getConfig() { + return config; + } + + /** + * Accessor for <code>SlaveRequest</code> to obtain the admin manager. + * + * @return the <code>AdminManager</code> + */ + public AdminManager getAdminManager() { + return manager; + } + + /** + * Accessor for the storage map which allow the test to + * maintain state across slave calls. + * + * @return the storage map + */ + public HashMap getStorageMap() { + return storageMap; + } + + private static class AutotRequestHandler implements Runnable { + + public void run() { + try { + ServerSocket socket = + new ServerSocket(InboundAutotRequest.PORT); + while (true) { + Socket requestSocket = socket.accept(); + logger.log(Level.FINER, "Got an external request"); + ObjectInputStream ois = + new ObjectInputStream(requestSocket.getInputStream()); + InboundAutotRequest request = + (InboundAutotRequest) ois.readObject(); + logger.log(Level.FINER, "Request is: " + request); + Object o = null; + try { + o = request.doRequest(config, manager); + } catch (Throwable e) { + e.fillInStackTrace(); + logger.log(Level.SEVERE, "Unexpected Exception ", e); + o = e; + } + ObjectOutputStream oos = + new ObjectOutputStream(requestSocket.getOutputStream()); + oos.writeObject(o); + oos.flush(); + oos.close(); + requestSocket.close();//redundant?? + } + } catch (Throwable e) { + e.fillInStackTrace(); + logger.log(Level.SEVERE, "Unexpected exception ", e); + } + } + } +} Modified: river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/Timeout.java URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/Timeout.java?rev=1634322&r1=1634321&r2=1634322&view=diff ============================================================================== --- river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/Timeout.java (original) +++ river/jtsk/skunk/qa_refactor/trunk/qa/src/com/sun/jini/qa/harness/Timeout.java Sun Oct 26 13:17:28 2014 @@ -1,190 +1,191 @@ -/* - * 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 com.sun.jini.qa.harness; - -import java.net.Socket; -import java.net.ServerSocket; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; - -/** - * Detects timeouts and calls a timeout handler. - */ -class Timeout implements Runnable { - - /** An interface defining the timeout behavior */ - static interface TimeoutHandler { - - /** - * Perform an action required for a timeout. - */ - public void handleTimeout(); - } - - /** the timeout handler to call after the timer expires */ - private TimeoutHandler timeoutHandler; - - /** the thread which implements the timeout interval */ - private Thread sleepThread; - - /** flag set when a timeout is detected */ - private boolean timedOut; - - /** the timeout interval */ - private long interval; - - /** - * Construct a timeout object to wait the given <code>interval</code> - * and interrupts the given <code>targetThread</code> if the - * interval passes before the <code>cancel</code> method is called. - * - * @param timeoutHandler the handler to call if a timeout is detected - * @param interval the timeout interval in milliseconds - */ - Timeout(TimeoutHandler timeoutHandler, int interval) { - this.timeoutHandler = timeoutHandler; - this.interval = interval; - } - - /** - * Starts the timeout clock - */ - void start() { - sleepThread = new Thread(this); - sleepThread.start(); - } - - /** - * Sleep for the timeout interval unless interrupted. If the - * sleep completes without interruption, the <code>timedOut</code> - * flag is set and the <code>TimeoutHandler.handleTimeout</code> method - * is called. Early returns from <code>Thread.sleep</code> will - * not cause premature indications of timeout. - */ - public void run() { - timedOut = false; - long startTime = new Date().getTime(); - long delta = 0; - while (delta < interval) { - try { - Thread.sleep(interval - delta); - } catch (InterruptedException e) { - return; - } - delta = new Date().getTime() - startTime; - } - timedOut = true; - timeoutHandler.handleTimeout(); - return; - } - - /** - * Cancel this timeout. An interrupt for this thread is fired. - */ - void cancel() { - sleepThread.interrupt(); - } - - /** - * Return the state of the <code>timedOut</code> flag. - * - * @return <code>true</code> if the most recent call to <code>run</code> - * resulted in a timeout. - */ - boolean timedOut() { - return timedOut; - } - - /** - * Handler for thread timeouts. The handled thread is interrupted. - */ - static class ThreadTimeoutHandler implements TimeoutHandler { - - /** the thread to interrupt */ - Thread thread; - - /** - * Construct the <code>TimeoutHandler</code>. The given - * <code>Thread</code> is interrupted. - */ - public ThreadTimeoutHandler(Thread thread) { - this.thread = thread; - } - - /** - * Interrupt the thread. - */ - public void handleTimeout() { - thread.interrupt(); - } - } - - /** - * Handler for socket timeouts. The handled socket is closed. - */ - static class ServerSocketTimeoutHandler implements TimeoutHandler { - - /** the socket to close */ - ServerSocket socket; - - /** - * Construct the <code>TimeoutHandler</code>. - */ - public ServerSocketTimeoutHandler(ServerSocket socket) { - this.socket = socket; - } - - /** - * close the socket. - */ - public void handleTimeout() { - try { - socket.close(); - } catch (IOException e) { - } - } - } - - /** - * Handler for InputStream timeouts. The handled stream is closed. - */ - static class InputStreamTimeoutHandler implements TimeoutHandler { - - /** the socket to close */ - InputStream stream; - - /** - * Construct the <code>TimeoutHandler</code>. - */ - public InputStreamTimeoutHandler(InputStream stream) { - this.stream = stream; - } - - /** - * close the stream. - */ - public void handleTimeout() { - try { - stream.close(); - } catch (IOException e) { - } - } - } -} +/* + * 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 com.sun.jini.qa.harness; + +import java.net.Socket; +import java.net.ServerSocket; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +/** + * Detects timeouts and calls a timeout handler. + */ +class Timeout implements Runnable { + + /** An interface defining the timeout behavior */ + static interface TimeoutHandler { + + /** + * Perform an action required for a timeout. + */ + public void handleTimeout(); + } + + /** the timeout handler to call after the timer expires */ + private TimeoutHandler timeoutHandler; + + /** the thread which implements the timeout interval */ + private Thread sleepThread; + + /** flag set when a timeout is detected */ + private boolean timedOut; + + /** the timeout interval */ + private long interval; + + /** + * Construct a timeout object to wait the given <code>interval</code> + * and interrupts the given <code>targetThread</code> if the + * interval passes before the <code>cancel</code> method is called. + * + * @param timeoutHandler the handler to call if a timeout is detected + * @param interval the timeout interval in milliseconds + */ + Timeout(TimeoutHandler timeoutHandler, int interval) { + this.timeoutHandler = timeoutHandler; + this.interval = interval; + } + + /** + * Starts the timeout clock + */ + void start() { + sleepThread = new Thread(this); + sleepThread.start(); + } + + /** + * Sleep for the timeout interval unless interrupted. If the + * sleep completes without interruption, the <code>timedOut</code> + * flag is set and the <code>TimeoutHandler.handleTimeout</code> method + * is called. Early returns from <code>Thread.sleep</code> will + * not cause premature indications of timeout. + */ + public void run() { + timedOut = false; + long startTime = new Date().getTime(); + long delta = 0; + while (delta < interval) { + try { + Thread.sleep(interval - delta); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + delta = new Date().getTime() - startTime; + } + timedOut = true; + timeoutHandler.handleTimeout(); + return; + } + + /** + * Cancel this timeout. An interrupt for this thread is fired. + */ + void cancel() { + sleepThread.interrupt(); + } + + /** + * Return the state of the <code>timedOut</code> flag. + * + * @return <code>true</code> if the most recent call to <code>run</code> + * resulted in a timeout. + */ + boolean timedOut() { + return timedOut; + } + + /** + * Handler for thread timeouts. The handled thread is interrupted. + */ + static class ThreadTimeoutHandler implements TimeoutHandler { + + /** the thread to interrupt */ + Thread thread; + + /** + * Construct the <code>TimeoutHandler</code>. The given + * <code>Thread</code> is interrupted. + */ + public ThreadTimeoutHandler(Thread thread) { + this.thread = thread; + } + + /** + * Interrupt the thread. + */ + public void handleTimeout() { + thread.interrupt(); + } + } + + /** + * Handler for socket timeouts. The handled socket is closed. + */ + static class ServerSocketTimeoutHandler implements TimeoutHandler { + + /** the socket to close */ + ServerSocket socket; + + /** + * Construct the <code>TimeoutHandler</code>. + */ + public ServerSocketTimeoutHandler(ServerSocket socket) { + this.socket = socket; + } + + /** + * close the socket. + */ + public void handleTimeout() { + try { + socket.close(); + } catch (IOException e) { + } + } + } + + /** + * Handler for InputStream timeouts. The handled stream is closed. + */ + static class InputStreamTimeoutHandler implements TimeoutHandler { + + /** the socket to close */ + InputStream stream; + + /** + * Construct the <code>TimeoutHandler</code>. + */ + public InputStreamTimeoutHandler(InputStream stream) { + this.stream = stream; + } + + /** + * close the stream. + */ + public void handleTimeout() { + try { + stream.close(); + } catch (IOException e) { + } + } + } +}
