1 /* 2 * $Id$ 3 * 4 * Copyright (c) 1996, 2018, 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 = 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 = 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 = 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 = 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<File> 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<File> 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<File, ZipFile> zips = new Hashtable<>(); 670 } 671 672 } 673