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 }