1 /* 2 * $Id$ 3 * 4 * Copyright (c) 2009, 2011, 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.services; 28 29 import com.sun.javatest.Harness; 30 import com.sun.javatest.Parameters; 31 import com.sun.javatest.TestDescription; 32 import com.sun.javatest.TestEnvironment; 33 import com.sun.javatest.TestResult; 34 import com.sun.javatest.TestResult.Fault; 35 import com.sun.javatest.TestSuite; 36 import com.sun.javatest.TestSuite.DuplicateLogNameFault; 37 import com.sun.javatest.TestSuite.NoSuchLogFault; 38 import com.sun.javatest.WorkDirectory; 39 import com.sun.javatest.services.Service.NotConnectedException; 40 import com.sun.javatest.services.Service.ServiceError; 41 import com.sun.javatest.tool.Command; 42 import com.sun.javatest.tool.CommandContext; 43 import com.sun.javatest.tool.CommandManager; 44 import com.sun.javatest.util.HelpTree; 45 import com.sun.javatest.util.HelpTree.Node; 46 import com.sun.javatest.util.I18NResourceBundle; 47 import java.io.IOException; 48 import java.io.InputStreamReader; 49 import java.io.Writer; 50 import java.util.HashMap; 51 import java.util.HashSet; 52 import java.util.Iterator; 53 import java.util.LinkedList; 54 import java.util.List; 55 import java.util.ListIterator; 56 import java.util.Map; 57 import java.util.Set; 58 import java.util.TreeSet; 59 import java.util.logging.Level; 60 import java.util.logging.Logger; 61 62 /** 63 * Class that manages all services. Has 1:1 relation with 64 * {@code com.sun.javatest.Harness} objects. Implements 65 * {@code Harness.Observer} interface to be notified of all {@code Harness} 66 * events, such as start and stop execution of all the test suite and each particular 67 * test. 68 */ 69 public class ServiceManager implements Harness.Observer { 70 71 private Harness harness; 72 73 private Map<String, Service> services; 74 private Set<String> activeServices; 75 76 private Set<TestPath> testServiceMap; 77 78 private StartMode mode = ServiceCommandManager.getMode(); 79 80 private WritingThread writer; 81 82 private WatchDog watchDog; 83 84 private Logger commonLog; 85 86 87 /** 88 * Enum of all supported service's starting modes. 89 * <p> 90 * {@code UP_FRONT} - all required services are started at the beginning of 91 * test run. 92 * <p> 93 * {@code LAZY} - each service is started, when first test, which requires 94 * this service, is going to be run. 95 * <p> 96 * {@code MANUALLY} - no one of services starts. 97 */ 98 public static enum StartMode { 99 LAZY("lazy"), UP_FRONT("up_front"), MANUALLY("manually"); 100 101 private String key; 102 103 StartMode(String s) { 104 key = s; 105 } 106 107 public String getKey() {return key;} 108 } 109 110 /** 111 * Constructor to create ServiceManager for services of given test suite. 112 * During initialization, reads from test suite information about specified 113 * services. 114 * @param ts test suite, which services will be managed. 115 */ 116 public ServiceManager(TestSuite ts) { 117 ServiceReader reader = ts.getServiceReader(); 118 services = reader.readServices(); 119 testServiceMap = reader.readTestServiceMap(); 120 for (TestPath p : testServiceMap) { 121 p.setServiceManager(this); 122 } 123 124 activeServices = new TreeSet<String>(); 125 126 writer = new WritingThread(); 127 writer.start(); 128 watchDog = new WatchDog(1000); 129 watchDog.start(); 130 } 131 132 /** 133 * Links this ServiceManager with {@link com.sun.javatest.Harness}. 134 * Set itself as Harness observer, creates logger for service-related 135 * messages. 136 * @param h {@link com.sun.javatest.Harness} to link with. 137 */ 138 public void setHarness(Harness h) { 139 this.harness = h; 140 harness.addObserver(this); 141 142 setParameters(harness.getParameters()); 143 } 144 145 public Harness getHarness() { 146 return harness; 147 } 148 149 public void setParameters(Parameters p) { 150 151 TestEnvironment env = p.getEnv(); 152 for (Service s : services.values()) { 153 s.createLog(p); 154 s.getProperties().setExternalProperties(env); 155 } 156 157 TestSuite ts = p.getTestSuite(); 158 WorkDirectory wd = p.getWorkDirectory(); 159 160 String name = "Services Common"; 161 try { 162 commonLog = ts.createLog(wd, null, name); 163 } catch (DuplicateLogNameFault ex) { 164 try { 165 commonLog = ts.getLog(wd, name); 166 } catch (NoSuchLogFault ex1) { 167 } 168 } 169 170 } 171 172 /** 173 * Allows to specify non-default output Writer for particular service. 174 * Default writer logs services output to JavaTest logging subsystem. 175 * @param sID string with service's ID. 176 * @param out Writer to use. 177 */ 178 public void setOutputWriter(String sID, Writer out) { 179 writer.setOutputWriter(sID, out); 180 } 181 182 /** 183 * Allows to specify non-default error Writer for particular service. 184 * Default writer logs services error output to JavaTest logging subsystem. 185 * @param sID string with service's ID. 186 * @param err Writer to use. 187 */ 188 public void setErrorWriter(String sID, Writer err) { 189 writer.setErrorWriter(sID, err); 190 } 191 192 193 194 /** 195 * Returns all services it manages. 196 * @return Map of all managed services, where keys are service's IDs and 197 * values are {@link com.sun.javatest.services.Service} objects. 198 */ 199 public Map<String, Service> getAllServices() { 200 return new HashMap<>(services); 201 } 202 203 /** 204 * Returns set of string with IDs for those services, which are required 205 * for current test run. 206 * @return IDs for currently used services. 207 */ 208 public Set<String> getActiveServices() { 209 return new TreeSet<>(activeServices); 210 } 211 212 public void startService(String sID) { 213 Service s = services.get(sID); 214 try { 215 if (s.isAlive()) { 216 return; 217 } 218 219 if (!s.start()) { 220 String msg = "Service " + sID + " was not started"; 221 commonLog.log(Level.WARNING, msg); 222 for (Observer o : watchDog.getObservers()) { 223 o.handleError(sID, new ServiceError(msg)); 224 } 225 } 226 else { 227 String msg = "Service " + sID + " was started"; 228 commonLog.log(Level.INFO, msg); 229 activeServices.add(sID); 230 writer.add(s); 231 } 232 233 } catch (NotConnectedException ex) { 234 String msg = "Service " + sID + " was not started because of" + 235 " connection failure\n"; 236 msg += ex.getMessage(); 237 commonLog.log(Level.WARNING, msg); 238 for (Observer o : watchDog.getObservers()) { 239 o.handleNotConnected(sID, ex); 240 } 241 } catch (ServiceError ex) { 242 String msg = "Service " + sID + " was not started because of" + 243 " service error\n"; 244 msg += ex.getMessage(); 245 commonLog.log(Level.WARNING, msg); 246 for (Observer o : watchDog.getObservers()) { 247 o.handleError(sID, ex); 248 } 249 } 250 251 watchDog.refreshServices(); 252 } 253 254 public void stopService(String sID) { 255 if (stopService0(sID)) { 256 activeServices.remove(sID); 257 } 258 } 259 260 private boolean stopService0(String sID) { 261 try { 262 Service serv = services.get(sID); 263 if (!serv.stop()) { 264 String msg = "Service " + sID + " was not stopped"; 265 commonLog.log(Level.WARNING, msg); 266 return false; 267 } else { 268 String msg = "Service " + sID + " was stopped"; 269 commonLog.log(Level.INFO, msg); 270 writer.remove(sID); 271 watchDog.refreshServices(); 272 return true; 273 } 274 } catch (NotConnectedException ex) { 275 String msg = "Service " + sID + " was not stopped because of" + 276 " connection failure\n"; 277 msg += ex.getMessage(); 278 commonLog.log(Level.WARNING, msg); 279 } catch (ServiceError ex) { 280 String msg = "Service " + sID + " was not stopped because of" + 281 " service error\n"; 282 msg += ex.getMessage(); 283 commonLog.log(Level.WARNING, msg); 284 } 285 286 return false; 287 } 288 289 public void startingTestRun(Parameters params) { 290 if (mode == StartMode.UP_FRONT) { 291 Set<String> active = new TreeSet<>(); 292 try { 293 Iterator<TestResult> iter = harness.getTestsIterator(null); 294 active = selectActiveServices(iter); 295 } catch (Harness.Fault f) { 296 commonLog.log(Level.SEVERE, f.getMessage()); 297 return; 298 } 299 300 for (String sID : active) { 301 startService(sID); 302 } 303 } 304 305 } 306 307 public synchronized void startingTest(TestResult tr) { 308 if (mode == StartMode.LAZY) { 309 try { 310 TestDescription td = tr.getDescription(); 311 312 for (TestPath p : testServiceMap) { 313 if (p.matches(td)) { 314 for (String sID : p.getServices()) { 315 316 /* If service was started previously and failed, 317 * it will be restarted. 318 */ 319 boolean isAlive = true; 320 try { 321 isAlive = services.get(sID).isAlive(); 322 } catch (NotConnectedException ex) { 323 isAlive = false; 324 } catch (ServiceError ex) { 325 isAlive = false; 326 } 327 328 if (!activeServices.contains(sID) || !isAlive) { 329 startService(sID); 330 } 331 } 332 } 333 } 334 } catch (Fault ex) { 335 } 336 } 337 } 338 339 340 public void finishedTest(TestResult tr) { 341 // Needs do nothing here for now; 342 } 343 344 public void stoppingTestRun() { 345 stopServices(); 346 } 347 348 public void finishedTesting() { 349 // Decided it's better to stop services right after test execution 350 stopServices(); 351 } 352 353 public void finishedTestRun(boolean allOK) { 354 // Or is it better to stop services after all post-processing done? 355 } 356 357 public void error(String msg) { 358 // Ignore test errors 359 } 360 361 /** 362 * Set service's start mode. 363 * @param mode one of supported 364 * {@link com.sun.javatest.service.ServiceManager#StartMode} values. 365 */ 366 public void setStartMode(StartMode mode) { 367 this.mode = mode; 368 } 369 370 private Set<String> selectActiveServices(Iterator<TestResult> iter) { 371 Set<String> active = new TreeSet<>(); 372 Set<TestPath> copy = new HashSet<>(testServiceMap); 373 TestResult tr; 374 TestDescription td; 375 while (!copy.isEmpty() && active.size() < services.size() && 376 (tr = iter.next()) != null ) { 377 try { 378 td = tr.getDescription(); 379 HashSet<TestPath> toRemove = new HashSet<>(); 380 for (TestPath p : copy) { 381 if (p.matches(td)) { 382 for (String sId : p.getServices()) { 383 active.add(sId); 384 } 385 toRemove.add(p); 386 } 387 } 388 copy.removeAll(toRemove); 389 } catch (Fault ex) {} 390 } 391 return active; 392 } 393 394 private synchronized void stopServices() { 395 Set<String> toRemove = new TreeSet<>(); 396 for (String s : activeServices) { 397 if (stopService0(s)) { 398 toRemove.add(s); 399 } 400 } 401 activeServices.removeAll(toRemove); 402 403 if (harness != null) { 404 harness.removeObserver(this); 405 harness = null; 406 } 407 408 writer.finish(); 409 } 410 411 private class WritingThread extends Thread { 412 private Map<String, Writer> outs = new HashMap<>(); 413 private Map<String, Writer> errs = new HashMap<>(); 414 415 private Map<String, InputStreamReader> sOuts = new HashMap<>(); 416 private Map<String, InputStreamReader> sErrs = new HashMap<>(); 417 418 public WritingThread() { 419 setDaemon(true); 420 } 421 422 public synchronized void add(Service s) { 423 String sID = ""; 424 try { 425 sID = s.getId(); 426 427 Writer w = outs.get(sID); 428 if (w == null) { 429 outs.put(sID, new LogWriter(s.getLog(), Level.ALL)); 430 } 431 InputStreamReader or = new InputStreamReader(s.getInputStream()); 432 sOuts.put(sID, or); 433 434 w = errs.get(sID); 435 if (w == null) { 436 errs.put(sID, new LogWriter(s.getLog(), Level.SEVERE)); 437 } 438 InputStreamReader er = new InputStreamReader(s.getErrorStream()); 439 sErrs.put(sID, er); 440 441 notifyAll(); 442 } catch (NotConnectedException ex) { 443 String msg = "Service :" + sID; 444 msg += "Error occurred when trying to access service streams.\n"; 445 msg += ex.getMessage(); 446 commonLog.log(Level.WARNING, msg); 447 } 448 } 449 450 public synchronized void remove(String sID) { 451 try { 452 Writer w = outs.get(sID); 453 if (w != null) { 454 w.flush(); 455 } 456 InputStreamReader r = sOuts.get(sID); 457 if (r != null) { 458 r.close(); 459 } 460 sOuts.remove(sID); 461 462 w = errs.get(sID); 463 if (w != null) { 464 w.flush(); 465 } 466 r = sErrs.get(sID); 467 if (r != null) { 468 r.close(); 469 } 470 sErrs.remove(sID); 471 } catch (IOException ex) { 472 } 473 } 474 475 public synchronized void setOutputWriter(String sID, Writer out) { 476 if (outs == null) { 477 outs = new HashMap<>(); 478 } 479 outs.put(sID, out); 480 } 481 482 public synchronized void setErrorWriter(String sID, Writer err) { 483 if (errs == null) { 484 errs = new HashMap<>(); 485 } 486 errs.put(sID, err); 487 } 488 489 490 synchronized void finish() { 491 for (String sID : outs.keySet()) { 492 remove(sID); 493 } 494 for (String sID : errs.keySet()) { 495 remove(sID); 496 } 497 } 498 499 public void run() { 500 while (true) { 501 synchronized (this) { 502 if (sOuts.size() == 0 && sErrs.size() == 0) { 503 try { 504 wait(); 505 } catch (InterruptedException ex) {} 506 } 507 } 508 write(outs, sOuts); 509 write(errs, sErrs); 510 try { 511 Thread.currentThread().sleep(1000); 512 } catch (InterruptedException ex) {} 513 } 514 } 515 516 private void write( 517 Map<String, Writer> wm, 518 Map<String, InputStreamReader> rm) { 519 520 char[] buf = new char[1000]; 521 522 List<String> toRemove = new LinkedList<>(); 523 524 String[] idCopy; 525 synchronized (this) { 526 idCopy = rm.keySet().toArray(new String[rm.keySet().size()]); 527 } 528 529 for (String sID : idCopy) { 530 InputStreamReader r = rm.get(sID); 531 Writer w = wm.get(sID); 532 if (r != null && w != null) { 533 int n = 0; 534 try { 535 n = r.read(buf); 536 if (n > 0) { 537 w.write(buf, 0, n); 538 } 539 } catch (IOException ex) {} 540 if (n == -1) { 541 toRemove.add(sID); 542 } 543 } 544 } 545 546 for (String sID : toRemove) { 547 synchronized (this) { 548 InputStreamReader r = rm.remove(sID); 549 if (r != null) { // reader could be removed by other thread 550 try { 551 r.close(); 552 } catch (IOException ex) { 553 } 554 } 555 } 556 } 557 } 558 559 } 560 561 private class WatchDog extends Thread { 562 private boolean stop; 563 private Set<Observer> observers = new HashSet<>(); 564 private int pause; 565 private Set<String> active; 566 private Object sync; 567 568 569 public WatchDog(int timeout) { 570 this.pause = timeout; 571 setDaemon(true); 572 active = getActiveServices(); 573 sync = new Object(); 574 } 575 576 public void addObserver(Observer o) { 577 observers.add(o); 578 } 579 public void removeObserver(Observer o) { 580 observers.remove(o); 581 } 582 public Set<Observer> getObservers() { 583 return observers; 584 } 585 586 public void refreshServices() { 587 active = getActiveServices(); 588 synchronized (sync) { 589 sync.notifyAll(); 590 } 591 } 592 593 public void run() { 594 stop = false; 595 boolean isAlive = true; 596 while (true) { 597 598 synchronized (sync) { 599 if (active.size() == 0) { 600 try { 601 sync.wait(); 602 } catch (InterruptedException ex) {} 603 } 604 } 605 606 for (String sID : active) { 607 try { 608 isAlive = services.get(sID).isAlive(); 609 // long time = System.currentTimeMillis(); 610 // System.out.println("Service: " + sID); 611 // System.out.println("TIME: " + time); 612 // System.out.println("IS_ALIVE: " + isAlive); 613 for (Observer o : observers) { 614 o.handleAlive(sID, isAlive); 615 } 616 } catch (NotConnectedException ex) { 617 for (Observer o : observers) { 618 o.handleNotConnected(sID, ex); 619 } 620 } catch (ServiceError ex) { 621 for (Observer o : observers) { 622 o.handleError(sID, ex); 623 } 624 } 625 } 626 try { 627 Thread.currentThread().sleep(1000); 628 } catch (InterruptedException ex) {} 629 } 630 } 631 632 } 633 634 public static interface Observer { 635 public void handleAlive(String sID, boolean alive); 636 public void handleNotConnected(String sID, NotConnectedException ex); 637 public void handleError(String sID, ServiceError ex); 638 } 639 640 public void addObserver(Observer o) { 641 if (watchDog != null) { 642 watchDog.addObserver(o); 643 } 644 } 645 646 public void removeObserver(Observer o) { 647 if (watchDog != null) { 648 watchDog.removeObserver(o); 649 } 650 } 651 652 public static class ServiceCommandManager extends CommandManager { 653 private static StartMode mode = StartMode.UP_FRONT; 654 655 public static StartMode getMode() { 656 return mode; 657 } 658 659 @Override 660 public Node getHelp() { 661 String[] cmds = { 662 ServiceStartCommand.getName() 663 }; 664 return new HelpTree.Node(i18n, "cmds", cmds); 665 } 666 667 @Override 668 public boolean parseCommand(String cmd, ListIterator<String> argIter, CommandContext ctx) throws Command.Fault { 669 if (isMatch(cmd, ServiceStartCommand.getName())) { 670 if (!argIter.hasNext()) { 671 return false; 672 } 673 String val = argIter.next(); 674 for (StartMode m : StartMode.values()) { 675 if (m.getKey().equalsIgnoreCase(val)) { 676 mode = m; 677 return true; 678 } 679 } 680 return false; 681 } 682 683 return false; 684 } 685 686 private static class ServiceStartCommand extends Command { 687 688 static String getName() { 689 return "startServices"; 690 } 691 692 ServiceStartCommand() { 693 super(getName()); 694 } 695 696 @Override 697 public void run(CommandContext ctx) throws Command.Fault {} 698 } 699 700 701 private I18NResourceBundle i18n = 702 I18NResourceBundle.getBundleForClass(this.getClass()); 703 } 704 705 }