       1 /*
       2  * $Id$
       3  *
       4  * Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
       5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       6  *
       7  * This code is free software; you can redistribute it and/or modify it
       8  * under the terms of the GNU General Public License version 2 only, as
       9  * published by the Free Software Foundation.  Oracle designates this
      10  * particular file as subject to the "Classpath" exception as provided
      11  * by Oracle in the LICENSE file that accompanied this code.
      12  *
      13  * This code is distributed in the hope that it will be useful, but WITHOUT
      14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      16  * version 2 for more details (a copy is included in the LICENSE file that
      17  * accompanied this code).
      18  *
      19  * You should have received a copy of the GNU General Public License version
      20  * 2 along with this work; if not, write to the Free Software Foundation,
      21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      22  *
      23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      24  * or visit www.oracle.com if you need additional information or have any
      25  * questions.
      26  */
      27 package com.sun.javatest.agent;
      28
      29 import java.io.BufferedInputStream;
      30 import java.io.BufferedOutputStream;
      31 import java.io.DataInputStream;
      32 import java.io.DataOutputStream;
      33 import java.io.EOFException;
      34 import java.io.File;
      35 import java.io.FileInputStream;
      36 import java.io.InputStream;
      37 import java.io.InterruptedIOException;
      38 import java.io.IOException;
      39 import java.io.PrintWriter;
      40 import java.net.ConnectException;
      41 import java.util.Enumeration;
      42 import java.util.Hashtable;
      43 import java.util.Vector;
      44 import java.util.zip.ZipEntry;
      45 import java.util.zip.ZipFile;
      46
      47 import com.sun.javatest.Status;
      48 import com.sun.javatest.util.DynamicArray;
      49
      50 /**
      51  * Access to the facilities provided by JT Harness agents.
      52  * Agents provide the ability to run remote and distributed tests.
      53  * The AgentManager provides a single point of control for creating
      54  * connections to agents, for managing a pool of available active agents,
      55  * and for registering observers for interesting events.
      56  **/
      57 public class AgentManager
      58 {
      59
      60     //--------------------------------------------------------------------------
      61
      62     /**
      63      * Access the one single instance of the AgentManager.
      64      * @return The one AgentManager.
      65      */
      66     public static AgentManager access() {
      67         return theManager;
      68     }
      69
      70     // private, so others can't create additional managers
      71     private AgentManager() {
      72     }
      73
      74     private static final AgentManager theManager = new AgentManager();
      75
      76     //--------------------------------------------------------------------------
      77
      78     /**
      79      * An Observer class to monitor Agent activity.
      80      */
      81     public static interface Observer {
      82         /**
      83          * Called when a task starts a request.
      84          * @param connection    The connection used to communicate with the agent.
      85          * @param tag           A tag used to identify the request.
      86          * @param request       The type of the request.
      87          * @param executable    The class to be executed.
      88          * @param args          Arguments to be passed to the class to be executed.
      89          * @param localizeArgs  Whether or not to localize the args remotely, using the
      90          *                      agent's map facility.
      91          */
      92         void started(Connection connection,
      93                      String tag, String request, String executable, String[] args,
      94                      boolean localizeArgs);
      95
      96         /**
      97          * Called when a task completes a request.
      98          * @param connection    The connection used to communicate with the agent.
      99          * @param status        The outcome of the request.
     100          */
     101         void finished(Connection connection, Status status);
     102     }
     103
     104     /**
     105      * Add an observer to monitor events.
     106      * @param o         The observer to be added.
     107      * @see #removeObserver
     108      */
     109     public synchronized void addObserver(Observer o) {
     110         observers = (Observer[])(DynamicArray.append(observers, o));
     111     }
     112
     113     /**
     114      * Remove an observer that had been previously registered to monitor events.
     115      * @param o         The observer to be removed.
     116      */
     117     public synchronized void removeObserver(Observer o) {
     118         observers = (Observer[])(DynamicArray.remove(observers, o));
     119     }
     120
     121     private synchronized void notifyStarted(Connection connection,
     122                                             String tag, String request, String executable, String[] args,
     123                                             boolean localizeArgs) {
     124         for (int i = 0; i < observers.length; i++) {
     125             observers[i].started(connection, tag, request, executable, args, localizeArgs);
     126         }
     127     }
     128
     129     private synchronized void notifyFinished(Connection connection, Status status) {
     130         for (int i = 0; i < observers.length; i++) {
     131             observers[i].finished(connection, status);
     132         }
     133     }
     134
     135     private Observer[] observers = new Observer[0];
     136
     137     //--------------------------------------------------------------------------
     138
     139     /**
     140      * Get the active agent pool, which is a holding area for
     141      * agents that have registered themselves as available for use
     142      * by the test harness.
     143      * @return the active agent pool
     144      */
     145     public ActiveAgentPool getActiveAgentPool() {
     146         return pool;
     147     }
     148
     149     private ActiveAgentPool pool = new ActiveAgentPool();
     150
     151     //--------------------------------------------------------------------------
     152
     153     /**
     154      * Create a task that will connect with an agent via a given connection.
     155      * @param c The connection to use to communicate with the agent.
     156      * @return a task object that can be used to initiate work on an agent
     157      * @throws IOException if a problem occurs establishing the connection
     158      */
     159     public Task connect(Connection c) throws IOException {
     160         return new Task(c);
     161     }
     162
     163     /**
     164      * Create a task that will connect to the next available active agent
     165      * that is available in a central pool.
     166      * @return a task object that can be used to initiate work on an agent
     167      * @throws ActiveAgentPool.NoAgentException if the pool has not been
     168      *                  initialized, or if there is still no agent after
     169      *                  a timeout period specified when the pool was initialized.
     170      * @throws InterruptedException if this thread is interrupted while waiting
     171      *                  for an agent to become available in the active agent pool.
     172      * @throws IOException if a problem occurs establishing the connection
     173      */
     174     public Task connectToActiveAgent() throws ActiveAgentPool.NoAgentException, InterruptedException, IOException {
     175         return connect(pool.nextAgent());
     176     }
     177
     178     /**
     179      * Create a task that will connect to a passive agent running on a
     180      * nominated host, and which is listening on the default port.
     181      * @param host      The host on which the agent should be running.
     182      * @return a task object that can be used to initiate work on an agent
     183      * @throws IOException is there is a problem connecting to the agent
     184      * @throws NullPointerException if host is null
     185      */
     186     public Task connectToPassiveAgent(String host) throws IOException {
     187         if (host == null) {
     188             throw new NullPointerException();
     189         }
     190
     191         return connectToPassiveAgent(host, Agent.defaultPassivePort);
     192     }
     193
     194
     195     /**
     196      * Create a connection to a passive agent running on a nominated host,
     197      * and which is listening on a specified port.
     198      * @param host      The host on which the agent should be running.
     199      * @param port      The port on which the agent should be listening.
     200      * @return a task object that can be used to initiate work on an agent
     201      * @throws IOException is there is a problem connecting to the agent
     202      * @throws NullPointerException if host is null
     203      */
     204     public Task connectToPassiveAgent(String host, int port) throws IOException {
     205         if (host == null) {
     206             throw new NullPointerException();
     207         }
     208
     209         for (int i = 0; ; i++) {
     210             try {
     211 //              return connect(new SocketConnection(host, port));
     212                 return connect(new InterruptableSocketConnection(host, port));
     213             }
     214             catch (ConnectException e) {
     215                 if (i == PASSIVE_AGENT_RETRY_LIMIT) {
     216                     throw e;
     217                 }
     218
     219                 try {
     220                     Thread.currentThread().sleep(5000);
     221                 }
     222                 catch (InterruptedException ignore) {
     223                 }
     224             }
     225         }
     226     }
     227
     228     private static final int PASSIVE_AGENT_RETRY_LIMIT = 12;
     229
     230
     231     //--------------------------------------------------------------------------
     232
     233     /**
     234      * A Task provides the ability to do work remotely on an agent.
     235      */
     236     public class Task {
     237
     238         /**
     239          * Create a connection to a agent retrieved from the agent pool.
     240          * @param c     The connection with which to communicate to the agent.
     241          */
     242         Task(Connection c) throws IOException {
     243             connection = c;
     244             in = new DataInputStream(new BufferedInputStream(c.getInputStream()));
     245             out = new DataOutputStream(new BufferedOutputStream(c.getOutputStream()));
     246         }
     247
     248         /**
     249          * Get the connection being used for this task.
     250          * @return the connection to the remote agent.
     251          */
     252         public Connection getConnection() {
     253             return connection;
     254         }
     255
     256         /**
     257          * Get the classpath to be used when loading classes on behalf of the
     258          * remote agent.
     259          * @return      An array of files and directories in which to look for classes to
     260          * be given to the remote agent.
     261          * @see #setClassPath(String)
     262          * @see #setClassPath(File[])
     263          */
     264         public File[] getClassPath() {
     265             return classPath;
     266         }
     267
     268         /**
     269          * Set the classpath to be used for incoming requests for classes
     270          * from the remote agent.
     271          * @param path  The classpath to be set, using the <em>local</em>
     272          *              file and path separator characters.
     273          * @see #getClassPath
     274          * @see #setClassPath(File[])
     275          */
     276         public void setClassPath(String path) {
     277             classPath = split(path);
     278         }
     279
     280
     281         /**
     282          * Set the classpath to be used for incoming requests for classes
     283          * from the remote agent.
     284          * @param path  An array of files, representing the path to be used.
     285          * @see #getClassPath
     286          * @see #setClassPath(String)
     287          */
     288         public void setClassPath(File[] path) {
     289             if (path == null) {
     290                 throw new NullPointerException();
     291             }
     292
     293             for (int i = 0; i < path.length; i++) {
     294                 if (path[i] == null) {
     295                     throw new IllegalArgumentException();
     296                 }
     297             }
     298
     299             classPath = path;
     300         }
     301
     302         /**
     303          * Set the shared classloader mode to be used for incoming requests for
     304          * classes from the remote agent.
     305          *
     306          * @param state The shared classloader mode, true to use a shared loader.
     307          */
     308         public void setSharedClassLoader(boolean state) {
     309             this.sharedCl = state;
     310         }
     311
     312         /**
     313          * Set the timeout after command thread will be interrupted on the agent
     314          * @param timeout value in seconds
     315          */
     316         public void setAgentCommandTimeout(int timeout) {
     317             this.timeout = timeout;
     318         }
     319
     320         /**
     321          * Request the agent for this client to execute a standard Test class.
     322          * @param tag   A string to identify the request in debug and trace output.
     323          * @param className The name of the class to execute. The class must be an
     324          *                      implementation of com.sun.javatest.Test, and must be
     325          *                      accessible by the agent: just the <em>name</em> of the class
     326          *                      is sent to the agent, not the classfile.
     327          * @param args  The arguments to be passed to the <code>run</code> method
     328          *                      of an instance of <code>classname</code> that will be executed
     329          *                      by the agent.
     330          * @param localizeArgs
     331          *                      Whether or not to instruct the agent to localize the args
     332          *                      to be passed to the test class. For example, this may be
     333          *                      necessary if the test has arguments that involve filenames
     334          *                      that differ from system to system.
     335          * @param log   A stream to which to write any data written to the log
     336          *                      stream when the test class is run.
     337          * @param ref   A stream to which to write any data written to the ref
     338          *                      stream when the test class is run.
     339          * @return              The status returned when the test class is run by the agent.
     340          * @see com.sun.javatest.Test
     341          */
     342         public Status executeTest(String tag, String className, String[] args,
     343                                   boolean localizeArgs,
     344                                   PrintWriter log, PrintWriter ref) {
     345             return run(tag, "executeTest", className, args, localizeArgs, log, ref);
     346         }
     347
     348         /**
     349          * Request the agent for this client to execute a standard Command class.
     350          * @param tag   A string to identify the request in debug and trace output.
     351          * @param className The name of the class to execute. The class must be an
     352          *                      implementation of com.sun.javatest.Command, and must be
     353          *                      accessible by the agent: just the <em>name</em> of the class
     354          *                      is sent to the agent, not the classfile.
     355          * @param args  The arguments to be passed to the <code>run</code> method
     356          *                      of an instance of <code>classname</code> that will be executed
     357          *                      by the agent.
     358          * @param localizeArgs
     359          *                      Whether or not to instruct the agent to localize the args
     360          *                      to be passed to the test class. For example, this may be
     361          *                      necessary if the test has arguments that involve filenames
     362          *                      that differ from system to system.
     363          * @param log   A stream to which to write any data written to the log
     364          *                      stream when the command class is run.
     365          * @param ref   A stream to which to write any data written to the ref
     366          *                      stream when the command class is run.
     367          * @return              The status returned when the command class is run by the agent.
     368          * @see com.sun.javatest.Command
     369          */
     370         public Status executeCommand(String tag, String className, String[] args,
     371                                      boolean localizeArgs,
     372                                      PrintWriter log, PrintWriter ref) {
     373             return run(tag, "executeCommand", className, args, localizeArgs, log, ref);
     374         }
     375
     376         /**
     377          * Request the agent for this client to execute the standard "main" method
     378          * for a class.
     379          * @param tag   A string to identify the request in debug and trace output.
     380          * @param className The name of the class to execute. The class must be an
     381          *                      implementation of com.sun.javatest.Command, and must be
     382          *                      accessible by the agent: just the <em>name</em> of the class
     383          *                      is sent to the agent, not the classfile.
     384          * @param args  The arguments to be passed to the <code>run</code> method
     385          *                      of an instance of <code>classname</code> that will be executed
     386          *                      by the agent.
     387          * @param localizeArgs
     388          *                      Whether or not to instruct the agent to localize the args
     389          *                      to be passed to the test class. For example, this may be
     390          *                      necessary if the test has arguments that involve filenames
     391          *                      that differ from system to system.
     392          * @param log   A stream to which to write any data written to the log
     393          *                      stream when the command class is run.
     394          * @param ref   A stream to which to write any data written to the ref
     395          *                      stream when the command class is run.
     396          * @return              Status.passed if the method terminated normally;
     397          *                      Status.failed if the method threw an exception;
     398          *                      Status.error if some other problem arose.
     399          * @see com.sun.javatest.Command
     400          */
     401         public Status executeMain(String tag, String className, String[] args,
     402                                      boolean localizeArgs,
     403                                      PrintWriter log, PrintWriter ref) {
     404             return run(tag, "executeMain", className, args, localizeArgs, log, ref);
     405         }
     406
     407         private Status run(String tag, String request, String executable, String[] args,
     408                            boolean localizeArgs,
     409                            PrintWriter log, PrintWriter ref) {
     410             notifyStarted(connection, tag, request, executable, args, localizeArgs);
     411             Status result = null;
     412             try {
     413 //                boolean sharedClOption = false;
     414                 out.writeShort(Agent.protocolVersion);
     415                 out.writeUTF(tag);
     416                 out.writeUTF(request);
     417                 out.writeUTF(executable);
     418                 out.writeShort(args.length);
     419                 for (int i = 0; i < args.length; i++) {
     420                     out.writeUTF(args[i]);
     421 //                    if ("-sharedCl".equalsIgnoreCase(args[i]) ||
     422 //                        "-sharedClassLoader".equalsIgnoreCase(args[i])) {
     423 //                        sharedClOption = true;
     424 //                    }
     425                 }
     426
     427                 out.writeBoolean(localizeArgs);
     428                 out.writeBoolean(classPath != null); // specify remoteClasses if classPath has been given
     429                 out.writeBoolean(sharedCl);
     430                 out.writeInt(timeout);
     431                 out.writeByte(0);
     432                 out.flush();
     433
     434                 result = readResults(log, ref);
     435             }
     436             catch (IOException e) {
     437                 try {
     438                     if (out != null)
     439                         out.close();
     440                     if (in != null)
     441                         in.close();
     442                 }
     443                 catch (IOException ignore) {
     444                 }
     445                 if (e instanceof InterruptedIOException)
     446                     result = Status.error("Communication with agent interrupted! (timed out?)." +
     447                             "\n InterruptedException: " + e);
     448                 else
     449                     result = Status.error("Problem communicating with agent: " + e);
     450             }
     451             finally {
     452                 notifyFinished(connection, result);
     453             }
     454             return result;
     455         }
     456
     457         private Status readResults(PrintWriter log, PrintWriter ref)
     458             throws IOException
     459         {
     460             Status status = null;
     461
     462             while (status == null) {
     463                 int code = in.read();
     464                 switch (code) {
     465                 case -1: // unexpected EOF
     466                     status = Status.error("premature EOF from agent");
     467                     break;
     468
     469                 case Agent.CLASS:
     470                     String className = in.readUTF();
     471                     //System.err.println("received request for " + className);
     472                     AgentRemoteClassData classData = locateClass(className);
     473                     classData.write(out);
     474                     out.flush();
     475                     break;
     476
     477                 case Agent.DATA:
     478                     String resourceName = in.readUTF();
     479                     //System.err.println("received request for " + resourceName);
     480                     byte[] resourceData = locateData(resourceName);
     481                     if (resourceData == null)
     482                         //System.err.println("resource not found: " + className);
     483                         out.writeInt(-1);
     484                     else {
     485                         out.writeInt(resourceData.length);
     486                         out.write(resourceData, 0, resourceData.length);
     487                     }
     488                     out.flush();
     489                     //System.err.println("done request for " + resourceName);
     490                     break;
     491
     492                 case Agent.STATUS:
     493                     int type = in.read();
     494                     String reason = in.readUTF();
     495                     switch (type) {
     496                     case Status.PASSED:
     497                         status = Status.passed(reason);
     498                         break;
     499                     case Status.FAILED:
     500                         status = Status.failed(reason);
     501                         break;
     502                     case Status.ERROR:
     503                         status = Status.error(reason);
     504                         break;
     505                     default:
     506                         status = Status.failed("Bad status from test: type=" + type + " reason=" + reason);
     507                         break;
     508                     }
     509                     break;
     510
     511                 case Agent.LOG:
     512                     log.write(in.readUTF());
     513                     break;
     514
     515                 case Agent.LOG_FLUSH:
     516                     log.write(in.readUTF());
     517                     log.flush();
     518                     break;
     519
     520                 case Agent.REF:
     521                     ref.write(in.readUTF());
     522                     break;
     523
     524                 case Agent.REF_FLUSH:
     525                     ref.write(in.readUTF());
     526                     ref.flush();
     527                     break;
     528                 }
     529             }
     530
     531             out.close();
     532             in.close();
     533             connection.close();
     534
     535             log.flush();
     536             ref.flush();
     537
     538             // might be better not to flush these ...
     539             for (Enumeration e = zips.keys(); e.hasMoreElements(); ) {
     540                 File f = (File)(e.nextElement());
     541                 ZipFile z = (ZipFile)(zips.get(f));
     542                 zips.remove(f);
     543                 z.close();
     544             }
     545
     546             return status;
     547         }
     548
     549         private AgentRemoteClassData locateClass(String name) {
     550             //System.err.println("locateClass: " + name);
     551             if (classPath != null) {
     552                 String cname = name.replace('.', '/') + ".class";
     553                 for (int i = 0; i < classPath.length; i++) {
     554                     byte[] data;
     555                     if (classPath[i].isDirectory())
     556                         data = readFromDir(cname, classPath[i]);
     557                     else
     558                         data = readFromJar(cname, classPath[i]);
     559                     if (data != null) {
     560                         String codeSource = "";
     561                         try {
     562                             codeSource = classPath[i].toURI().toURL().getPath();
     563                         } catch (IOException e) {
     564                             //codeSource will not be set
     565                         }
     566                         AgentRemoteClassData classData = new AgentRemoteClassData(name, codeSource, data);
     567                         return classData;
     568                     }
     569                 }
     570             }
     571
     572             return AgentRemoteClassData.NO_CLASS_DATA;
     573         }
     574
     575         private byte[] locateData(String name) {
     576             //System.err.println("locateData: " + name);
     577             if (classPath != null) {
     578                 for (int i = 0; i < classPath.length; i++) {
     579                     byte[] data;
     580                     if (classPath[i].isDirectory())
     581                         data = readFromDir(name, classPath[i]);
     582                     else
     583                         data = readFromJar(name, classPath[i]);
     584                     if (data != null)
     585                         return data;
     586                 }
     587             }
     588
     589             return null;
     590         }
     591
     592         private byte[] readFromDir(String name, File dir) {
     593             //System.err.println("readFromDir: " + name + " " + dir);
     594             try {
     595                 File file = new File(dir, name);
     596                 return read(new FileInputStream(file), ((int) file.length()));
     597             }
     598             catch (IOException e) {
     599                 //System.err.println("readFromDir: " + e);
     600                 return null;
     601             }
     602         }
     603
     604         private byte[] readFromJar(String name, File jarFile) {
     605             //System.err.println("readFromJar: " + name + " " + jarFile);
     606             try {
     607                 ZipFile z = (ZipFile)zips.get(jarFile);
     608                 if (z == null) {
     609                     z = new ZipFile(jarFile);
     610                     zips.put(jarFile, z);
     611                 }
     612                 ZipEntry ze = z.getEntry(name);
     613                 if (ze == null)
     614                     return null;
     615                 return read(z.getInputStream(ze), ((int) ze.getSize()));
     616             }
     617             catch (IOException e) {
     618                 //System.err.println("readFromJar: " + e);
     619                 return null;
     620             }
     621         }
     622
     623         private byte[] read(InputStream in, int size) throws IOException {
     624             //System.err.println("read: " + size);
     625             try {
     626                 byte data[] = new byte[size];
     627                 for (int total = 0; total < data.length; ) {
     628                     int n = in.read(data, total, data.length - total);
     629                     if (n > 0)
     630                         total += n;
     631                     else
     632                         throw new EOFException("unexpected end of file");
     633                 }
     634                 //System.err.println("read complete: " + data.length);
     635                 return data;
     636             }
     637             finally {
     638                 in.close();
     639             }
     640         }
     641
     642         private File[] split(String s) {
     643             char pathCh = File.pathSeparatorChar;
     644             Vector v = new Vector();
     645             int start = 0;
     646             for (int i = s.indexOf(pathCh); i != -1; i = s.indexOf(pathCh, start)) {
     647                 add(s.substring(start, i), v);
     648                 start = i + 1;
     649             }
     650             if (start != s.length())
     651                 add(s.substring(start), v);
     652             File[] path = new File[v.size()];
     653             v.copyInto(path);
     654             return path;
     655         }
     656
     657         private void add(String s, Vector v) {
     658             if (s.length() != 0)
     659                 v.addElement(new File(s));
     660         }
     661
     662         private Connection connection;
     663         private DataInputStream in;
     664         private DataOutputStream out;
     665
     666         private File[] classPath;
     667         private boolean sharedCl;
     668         private int timeout = 0;
     669         private Hashtable zips = new Hashtable();
     670     }
     671
     672 }
     673
    --- EOF ---