Repository: incubator-slider
Updated Branches:
  refs/heads/feature/SLIDER-394_env_check [created] 0f67b1042


SLIDER-394 slider client and AM services to fail-fast if the hadoop 
dependencies are missing


Project: http://git-wip-us.apache.org/repos/asf/incubator-slider/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-slider/commit/0f67b104
Tree: http://git-wip-us.apache.org/repos/asf/incubator-slider/tree/0f67b104
Diff: http://git-wip-us.apache.org/repos/asf/incubator-slider/diff/0f67b104

Branch: refs/heads/feature/SLIDER-394_env_check
Commit: 0f67b10424cb807aebb3d2b15c3b24149f4ae70a
Parents: 29a40f5
Author: Steve Loughran <ste...@apache.org>
Authored: Thu Sep 4 16:28:20 2014 +0100
Committer: Steve Loughran <ste...@apache.org>
Committed: Thu Sep 4 16:28:20 2014 +0100

----------------------------------------------------------------------
 .../apache/slider/common/tools/SliderUtils.java | 555 +++++++++++++------
 .../services/utility/EndOfServiceWaiter.java    |  84 +++
 .../services/workflow/ForkedProcessService.java |  21 +-
 .../services/workflow/LongLivedProcess.java     |  38 +-
 .../tools/TestExecutionEnvironment.groovy       |  42 ++
 .../common/tools/TestWindowsSupport.groovy      |   4 +-
 .../services/workflow/EndOfServiceWaiter.java   |  56 --
 .../TestWorkflowForkedProcessService.java       |   4 +-
 8 files changed, 570 insertions(+), 234 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/0f67b104/slider-core/src/main/java/org/apache/slider/common/tools/SliderUtils.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/common/tools/SliderUtils.java 
b/slider-core/src/main/java/org/apache/slider/common/tools/SliderUtils.java
index eb214db..44fe4fd 100644
--- a/slider-core/src/main/java/org/apache/slider/common/tools/SliderUtils.java
+++ b/slider-core/src/main/java/org/apache/slider/common/tools/SliderUtils.java
@@ -56,7 +56,10 @@ import org.apache.slider.core.exceptions.ErrorStrings;
 import org.apache.slider.core.exceptions.MissingArgException;
 import org.apache.slider.core.exceptions.SliderException;
 import org.apache.slider.core.launch.ClasspathConstructor;
+import org.apache.slider.core.main.LauncherExitCodes;
+import org.apache.slider.server.services.utility.EndOfServiceWaiter;
 import org.apache.slider.server.services.utility.PatternValidator;
+import org.apache.slider.server.services.workflow.ForkedProcessService;
 import org.apache.zookeeper.server.util.KerberosUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -67,6 +70,7 @@ import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InterruptedIOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.net.InetSocketAddress;
@@ -88,6 +92,7 @@ import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.TreeSet;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -102,12 +107,24 @@ public final class SliderUtils {
    * turned on (prevents re-entrancy)
    */
   private static final AtomicBoolean processSecurityAlreadyInitialized =
-    new AtomicBoolean(false);
+      new AtomicBoolean(false);
   public static final String JAVA_SECURITY_KRB5_REALM =
       "java.security.krb5.realm";
   public static final String JAVA_SECURITY_KRB5_KDC = "java.security.krb5.kdc";
+
+  /**
+   * Winutils
+   */
   public static final String WINUTILS = "WINUTILS.EXE";
+  /**
+   * name of openssl program
+   */
+  public static final String OPENSSL = "openssl";
 
+  /**
+   * name of python program
+   */
+  public static final String PYTHON = "python";
 
   private SliderUtils() {
   }
@@ -131,7 +148,8 @@ public final class SliderUtils {
    * @param msg the message to be shown in exception
    */
   @SuppressWarnings("ResultOfMethodCallIgnored")
-  private static void validateNumber(String num, String msg)  throws 
BadConfigException {
+  private static void validateNumber(String num, String msg) throws
+      BadConfigException {
     try {
       Integer.parseInt(num);
     } catch (NumberFormatException nfe) {
@@ -145,15 +163,16 @@ public final class SliderUtils {
    * @param heapsize
    * @return heapsize in MB
    */
-  public static String translateTrailingHeapUnit(String heapsize) throws 
BadConfigException {
+  public static String translateTrailingHeapUnit(String heapsize) throws
+      BadConfigException {
     String errMsg = "Bad heapsize: ";
     if (heapsize.endsWith("m") || heapsize.endsWith("M")) {
-      String num = heapsize.substring(0, heapsize.length()-1);
+      String num = heapsize.substring(0, heapsize.length() - 1);
       validateNumber(num, errMsg);
       return num;
     }
     if (heapsize.endsWith("g") || heapsize.endsWith("G")) {
-      String num = heapsize.substring(0, heapsize.length()-1)+"000";
+      String num = heapsize.substring(0, heapsize.length() - 1) + "000";
       validateNumber(num, errMsg);
       return num;
     }
@@ -222,7 +241,7 @@ public final class SliderUtils {
     ClassLoader loader = my_class.getClassLoader();
     if (loader == null) {
       throw new IOException(
-        "Class " + my_class + " does not have a classloader!");
+          "Class " + my_class + " does not have a classloader!");
     }
     String class_file = my_class.getName().replaceAll("\\.", "/") + ".class";
     Enumeration<URL> urlEnumeration = loader.getResources(class_file);
@@ -255,16 +274,16 @@ public final class SliderUtils {
   }
 
   public static void checkPort(String hostname, int port, int connectTimeout)
-    throws IOException {
+      throws IOException {
     InetSocketAddress addr = new InetSocketAddress(hostname, port);
     checkPort(hostname, addr, connectTimeout);
   }
 
   @SuppressWarnings("SocketOpenedButNotSafelyClosed")
   public static void checkPort(String name,
-                               InetSocketAddress address,
-                               int connectTimeout)
-    throws IOException {
+      InetSocketAddress address,
+      int connectTimeout)
+      throws IOException {
     Socket socket = null;
     try {
       socket = new Socket();
@@ -274,14 +293,14 @@ public final class SliderUtils {
                             + " at " + address
                             + " after " + connectTimeout + "millisconds"
                             + ": " + e,
-                            e);
+          e);
     } finally {
       IOUtils.closeSocket(socket);
     }
   }
 
   public static void checkURL(String name, String url, int timeout) throws
-                                                                    
IOException {
+      IOException {
     InetSocketAddress address = NetUtils.createSocketAddr(url);
     checkPort(name, address, timeout);
   }
@@ -294,22 +313,22 @@ public final class SliderUtils {
    * @return the file
    */
   public static File requiredFile(String filename, String role) throws
-                                                                IOException {
+      IOException {
     if (filename.isEmpty()) {
       throw new ExitUtil.ExitException(-1, role + " file not defined");
     }
     File file = new File(filename);
     if (!file.exists()) {
       throw new ExitUtil.ExitException(-1,
-                                       role + " file not found: " +
-                                       file.getCanonicalPath());
+          role + " file not found: " +
+          file.getCanonicalPath());
     }
     return file;
   }
 
   private static final PatternValidator clusternamePattern
       = new PatternValidator("[a-z][a-z0-9_-]*");
-      
+
   /**
    * Normalize a cluster name then verify that it is valid
    * @param name proposed cluster name
@@ -318,12 +337,13 @@ public final class SliderUtils {
   public static boolean isClusternameValid(String name) {
     return name != null && clusternamePattern.matches(name);
   }
+
   public static boolean oldIsClusternameValid(String name) {
     if (name == null || name.isEmpty()) {
       return false;
     }
     int first = name.charAt(0);
-    if (0 == (Character.getType(first)  & Character.LOWERCASE_LETTER)) {
+    if (0 == (Character.getType(first) & Character.LOWERCASE_LETTER)) {
       return false;
     }
 
@@ -354,11 +374,11 @@ public final class SliderUtils {
    * @return # of files copies
    */
   public static int copyDirectory(Configuration conf,
-                                  Path srcDirPath,
-                                  Path destDirPath,
-                                  FsPermission permission) throws
-                                                           IOException,
-                                                           
BadClusterStateException {
+      Path srcDirPath,
+      Path destDirPath,
+      FsPermission permission) throws
+      IOException,
+      BadClusterStateException {
     FileSystem srcFS = FileSystem.get(srcDirPath.toUri(), conf);
     FileSystem destFS = FileSystem.get(destDirPath.toUri(), conf);
     //list all paths in the src.
@@ -366,7 +386,8 @@ public final class SliderUtils {
       throw new FileNotFoundException("Source dir not found " + srcDirPath);
     }
     if (!srcFS.isDirectory(srcDirPath)) {
-      throw new FileNotFoundException("Source dir not a directory " + 
srcDirPath);
+      throw new FileNotFoundException(
+          "Source dir not a directory " + srcDirPath);
     }
     GlobFilter dotFilter = new GlobFilter("[!.]*");
     FileStatus[] entries = srcFS.listStatus(srcDirPath, dotFilter);
@@ -378,7 +399,8 @@ public final class SliderUtils {
       permission = FsPermission.getDirDefault();
     }
     if (!destFS.exists(destDirPath)) {
-      new SliderFileSystem(destFS, conf).createWithPermissions(destDirPath, 
permission);
+      new SliderFileSystem(destFS, conf).createWithPermissions(destDirPath,
+          permission);
     }
     Path[] sourcePaths = new Path[srcFileCount];
     for (int i = 0; i < srcFileCount; i++) {
@@ -394,8 +416,8 @@ public final class SliderUtils {
       sourcePaths[i] = srcFile;
     }
     log.debug("Copying {} files from {} to dest {}", srcFileCount,
-              srcDirPath,
-              destDirPath);
+        srcDirPath,
+        destDirPath);
     FileUtil.copy(srcFS, sourcePaths, destFS, destDirPath, false, true, conf);
     return srcFileCount;
   }
@@ -430,7 +452,8 @@ public final class SliderUtils {
 
     //if the fallback option is NOT set, enable it.
     //if it is explicitly set to anything -leave alone
-    if (conf.get(SliderXmlConfKeys.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH) == 
null) {
+    if (conf.get(SliderXmlConfKeys.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH) ==
+        null) {
       conf.set(SliderXmlConfKeys.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH, "true");
     }
     return conf;
@@ -469,7 +492,9 @@ public final class SliderUtils {
    * @param trailing add a trailing entry or not
    * @return the joined entries
    */
-  public static String join(Collection collection, String separator, boolean 
trailing) {
+  public static String join(Collection collection,
+      String separator,
+      boolean trailing) {
     StringBuilder b = new StringBuilder();
     // fast return on empty collection
     if (collection.isEmpty()) {
@@ -481,9 +506,9 @@ public final class SliderUtils {
     }
     int length = separator.length();
     String s = b.toString();
-    return (trailing || s.isEmpty())?
+    return (trailing || s.isEmpty()) ?
            s
-           : (b.substring(0, b.length() - length));
+                                     : (b.substring(0, b.length() - length));
   }
 
   /**
@@ -495,9 +520,10 @@ public final class SliderUtils {
    */
   public static String join(String[] collection, String separator) {
     return join(collection, separator, true);
-    
-    
+
+
   }
+
   /**
    * Join an array of strings with a separator that appears after every
    * instance in the list -optionally at the end
@@ -507,7 +533,7 @@ public final class SliderUtils {
    * @return the joined entries
    */
   public static String join(String[] collection, String separator,
-                            boolean trailing) {
+      boolean trailing) {
     return join(Arrays.asList(collection), separator, trailing);
   }
 
@@ -519,7 +545,7 @@ public final class SliderUtils {
    * @return the list
    */
   public static String joinWithInnerSeparator(String separator,
-                                              Object... collection) {
+      Object... collection) {
     StringBuilder b = new StringBuilder();
     boolean first = true;
 
@@ -543,10 +569,15 @@ public final class SliderUtils {
     return v;
   }
 
-  public static String appReportToString(ApplicationReport r, String 
separator) {
+  public static String appReportToString(ApplicationReport r,
+      String separator) {
     StringBuilder builder = new StringBuilder(512);
-    builder.append("application ").append(
-        
r.getName()).append("/").append(r.getApplicationType()).append(separator);
+    builder.append("application ")
+           .append(
+               r.getName())
+           .append("/")
+           .append(r.getApplicationType())
+           .append(separator);
     Set<String> tags = r.getApplicationTags();
     if (!tags.isEmpty()) {
       for (String tag : tags) {
@@ -555,12 +586,20 @@ public final class SliderUtils {
     }
     builder.append("state: ").append(r.getYarnApplicationState());
     builder.append(separator).append("URL: ").append(r.getTrackingUrl());
-    builder.append(separator).append("Started ").append(new 
Date(r.getStartTime()).toGMTString());
+    builder.append(separator)
+           .append("Started ")
+           .append(new Date(r.getStartTime()).toGMTString());
     long finishTime = r.getFinishTime();
-    if (finishTime>0) {
-      builder.append(separator).append("Finished ").append(new 
Date(finishTime).toGMTString());
-    }
-    builder.append(separator).append("RPC 
:").append(r.getHost()).append(':').append(r.getRpcPort());
+    if (finishTime > 0) {
+      builder.append(separator)
+             .append("Finished ")
+             .append(new Date(finishTime).toGMTString());
+    }
+    builder.append(separator)
+           .append("RPC :")
+           .append(r.getHost())
+           .append(':')
+           .append(r.getRpcPort());
     String diagnostics = r.getDiagnostics();
     if (!diagnostics.isEmpty()) {
       builder.append(separator).append("Diagnostics :").append(diagnostics);
@@ -575,8 +614,8 @@ public final class SliderUtils {
    * @param second the map that is merged in
    * @return the first map
    */
-  public static Map<String, String>  mergeMap(Map<String, String> first,
-           Map<String, String> second) {
+  public static Map<String, String> mergeMap(Map<String, String> first,
+      Map<String, String> second) {
     first.putAll(second);
     return first;
   }
@@ -589,8 +628,8 @@ public final class SliderUtils {
    * @return dest -with the entries merged in
    */
   public static Map<String, String> mergeEntries(Map<String, String> dest,
-                                                 Iterable<Map.Entry<String, 
String>> entries) {
-    for (Map.Entry<String, String> entry: entries) {
+      Iterable<Map.Entry<String, String>> entries) {
+    for (Map.Entry<String, String> entry : entries) {
       dest.put(entry.getKey(), entry.getValue());
     }
     return dest;
@@ -604,8 +643,8 @@ public final class SliderUtils {
    * @param <T2> value type
    * @return 'first' merged with the second
    */
-  public static <T1, T2> Map<T1, T2>  mergeMaps(Map<T1, T2> first,
-           Map<T1, T2> second) {
+  public static <T1, T2> Map<T1, T2> mergeMaps(Map<T1, T2> first,
+      Map<T1, T2> second) {
     first.putAll(second);
     return first;
   }
@@ -619,7 +658,7 @@ public final class SliderUtils {
    * @return 'first' merged with the second
    */
   public static <T1, T2> Map<T1, T2> mergeMapsIgnoreDuplicateKeys(Map<T1, T2> 
first,
-                                                                  Map<T1, T2> 
second) {
+      Map<T1, T2> second) {
     Preconditions.checkArgument(first != null, "Null 'first' value");
     Preconditions.checkArgument(second != null, "Null 'second' value");
     for (Map.Entry<T1, T2> entry : second.entrySet()) {
@@ -637,8 +676,8 @@ public final class SliderUtils {
    * @return a string representation of the map
    */
   public static String stringifyMap(Map<String, String> map) {
-    StringBuilder builder =new StringBuilder();
-    for (Map.Entry<String, String> entry: map.entrySet()) {
+    StringBuilder builder = new StringBuilder();
+    for (Map.Entry<String, String> entry : map.entrySet()) {
       builder.append(entry.getKey())
              .append("=\"")
              .append(entry.getValue())
@@ -659,11 +698,11 @@ public final class SliderUtils {
    * @throws BadConfigException if the value could not be parsed
    */
   public static int getIntValue(Map<String, String> roleMap,
-                         String key,
-                         int defVal,
-                         int min,
-                         int max
-                        ) throws BadConfigException {
+      String key,
+      int defVal,
+      int min,
+      int max
+  ) throws BadConfigException {
     String valS = roleMap.get(key);
     return parseAndValidate(key, valS, defVal, min, max);
 
@@ -679,10 +718,10 @@ public final class SliderUtils {
    * @throws BadConfigException if the value could not be parsed
    */
   public static int parseAndValidate(String errorKey,
-                                     String valS,
-                                     int defVal,
-                                     int min, int max) throws
-                                                       BadConfigException {
+      String valS,
+      int defVal,
+      int min, int max) throws
+      BadConfigException {
     if (valS == null) {
       valS = Integer.toString(defVal);
     }
@@ -709,14 +748,14 @@ public final class SliderUtils {
 
   public static InetSocketAddress getRmAddress(Configuration conf) {
     return conf.getSocketAddr(YarnConfiguration.RM_ADDRESS,
-                              YarnConfiguration.DEFAULT_RM_ADDRESS,
-                              YarnConfiguration.DEFAULT_RM_PORT);
+        YarnConfiguration.DEFAULT_RM_ADDRESS,
+        YarnConfiguration.DEFAULT_RM_PORT);
   }
 
   public static InetSocketAddress getRmSchedulerAddress(Configuration conf) {
     return conf.getSocketAddr(YarnConfiguration.RM_SCHEDULER_ADDRESS,
-                              YarnConfiguration.DEFAULT_RM_SCHEDULER_ADDRESS,
-                              YarnConfiguration.DEFAULT_RM_SCHEDULER_PORT);
+        YarnConfiguration.DEFAULT_RM_SCHEDULER_ADDRESS,
+        YarnConfiguration.DEFAULT_RM_SCHEDULER_PORT);
   }
 
   /**
@@ -759,11 +798,11 @@ public final class SliderUtils {
       return "null container";
     }
     return String.format(Locale.ENGLISH,
-                         "ContainerID=%s nodeID=%s http=%s priority=%s",
-                         container.getId(),
-                         container.getNodeId(),
-                         container.getNodeHttpAddress(),
-                         container.getPriority());
+        "ContainerID=%s nodeID=%s http=%s priority=%s",
+        container.getId(),
+        container.getNodeId(),
+        container.getNodeHttpAddress(),
+        container.getPriority());
   }
 
   /**
@@ -844,12 +883,12 @@ public final class SliderUtils {
   public static Map<String, String> buildEnvMap(Map<String, String> roleOpts) {
     Map<String, String> env = new HashMap<String, String>();
     if (roleOpts != null) {
-      for (Map.Entry<String, String> entry: roleOpts.entrySet()) {
+      for (Map.Entry<String, String> entry : roleOpts.entrySet()) {
         String key = entry.getKey();
         if (key.startsWith(RoleKeys.ENV_PREFIX)) {
           String envName = key.substring(RoleKeys.ENV_PREFIX.length());
           if (!envName.isEmpty()) {
-            env.put(envName,entry.getValue());
+            env.put(envName, entry.getValue());
           }
         }
       }
@@ -863,8 +902,8 @@ public final class SliderUtils {
    * @param commandOptions command opts
    */
   public static void applyCommandLineRoleOptsToRoleMap(Map<String, Map<String, 
String>> clusterRoleMap,
-                                                       Map<String, Map<String, 
String>> commandOptions) {
-    for (Map.Entry<String, Map<String, String>> entry: 
commandOptions.entrySet()) {
+      Map<String, Map<String, String>> commandOptions) {
+    for (Map.Entry<String, Map<String, String>> entry : 
commandOptions.entrySet()) {
       String key = entry.getKey();
       Map<String, String> optionMap = entry.getValue();
       Map<String, String> existingMap = clusterRoleMap.get(key);
@@ -872,7 +911,7 @@ public final class SliderUtils {
         existingMap = new HashMap<String, String>();
       }
       log.debug("Overwriting role options with command line values {}",
-                stringifyMap(optionMap));
+          stringifyMap(optionMap));
       mergeMap(existingMap, optionMap);
       //set or overwrite the role
       clusterRoleMap.put(key, existingMap);
@@ -885,10 +924,10 @@ public final class SliderUtils {
    * @throws BadCommandArgumentsException if it is invalid
    */
   public static void validateClusterName(String clustername) throws
-                                                         
BadCommandArgumentsException {
+      BadCommandArgumentsException {
     if (!isClusternameValid(clustername)) {
       throw new BadCommandArgumentsException(
-        "Illegal cluster name: " + clustername);
+          "Illegal cluster name: " + clustername);
     }
   }
 
@@ -900,12 +939,12 @@ public final class SliderUtils {
    * @throws BadConfigException if the key is not set
    */
   public static void verifyPrincipalSet(Configuration conf,
-                                        String principal) throws
-                                                           BadConfigException {
+      String principal) throws
+      BadConfigException {
     String principalName = conf.get(principal);
     if (principalName == null) {
       throw new BadConfigException("Unset Kerberos principal : %s",
-                                   principal);
+          principal);
     }
     log.debug("Kerberos princial {}={}", principal, principalName);
   }
@@ -928,8 +967,8 @@ public final class SliderUtils {
    * @throws BadConfigException the configuration/process is invalid
    */
   public static boolean maybeInitSecurity(Configuration conf) throws
-                                                              IOException,
-                                                              
BadConfigException {
+      IOException,
+      BadConfigException {
     boolean clusterSecure = isHadoopClusterSecure(conf);
     if (clusterSecure) {
       log.debug("Enabling security");
@@ -946,8 +985,8 @@ public final class SliderUtils {
    * @throws BadConfigException the configuration and system state are 
inconsistent
    */
   public static boolean initProcessSecurity(Configuration conf) throws
-                                                                IOException,
-                                                                
BadConfigException {
+      IOException,
+      BadConfigException {
 
     if (processSecurityAlreadyInitialized.compareAndSet(true, true)) {
       //security is already inited
@@ -959,9 +998,9 @@ public final class SliderUtils {
     //this gets UGI to reset its previous world view (i.e simple auth)
     //security
     log.debug("java.security.krb5.realm={}",
-              System.getProperty(JAVA_SECURITY_KRB5_REALM, ""));
+        System.getProperty(JAVA_SECURITY_KRB5_REALM, ""));
     log.debug("java.security.krb5.kdc={}",
-              System.getProperty(JAVA_SECURITY_KRB5_KDC, ""));
+        System.getProperty(JAVA_SECURITY_KRB5_KDC, ""));
     log.debug("hadoop.security.authentication={}",
         
conf.get(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION));
     log.debug("hadoop.security.authorization={}",
@@ -974,14 +1013,14 @@ public final class SliderUtils {
     log.debug("Login user is {}", UserGroupInformation.getLoginUser());
     if (!UserGroupInformation.isSecurityEnabled()) {
       throw new BadConfigException("Although secure mode is enabled," +
-               "the application has already set up its user as an insecure 
entity %s",
-               authUser);
+                                   "the application has already set up its 
user as an insecure entity %s",
+          authUser);
     }
     if (authUser.getAuthenticationMethod() ==
         UserGroupInformation.AuthenticationMethod.SIMPLE) {
       throw new BadConfigException("Auth User is not Kerberized %s" +
-       " -security has already been set up with the wrong authentication 
method",
-                       authUser);
+                                   " -security has already been set up with 
the wrong authentication method",
+          authUser);
 
     }
 
@@ -1017,24 +1056,25 @@ public final class SliderUtils {
    * @throws IOException trouble copying to HDFS
    */
   public static LocalResource putJar(Map<String, LocalResource> 
providerResources,
-                              SliderFileSystem sliderFileSystem,
-                              Class clazz,
-                              Path tempPath,
-                              String libdir,
-                              String jarName
-                             )
-    throws IOException, SliderException {
+      SliderFileSystem sliderFileSystem,
+      Class clazz,
+      Path tempPath,
+      String libdir,
+      String jarName
+  )
+      throws IOException, SliderException {
     LocalResource res = sliderFileSystem.submitJarWithClass(
-            clazz,
-            tempPath,
-            libdir,
-            jarName);
-    providerResources.put(libdir + "/"+ jarName, res);
+        clazz,
+        tempPath,
+        libdir,
+        jarName);
+    providerResources.put(libdir + "/" + jarName, res);
     return res;
   }
 
-    public static Map<String, Map<String, String>> deepClone(Map<String, 
Map<String, String>> src) {
-    Map<String, Map<String, String>> dest = new HashMap<String, Map<String, 
String>>();
+  public static Map<String, Map<String, String>> deepClone(Map<String, 
Map<String, String>> src) {
+    Map<String, Map<String, String>> dest =
+        new HashMap<String, Map<String, String>>();
     for (Map.Entry<String, Map<String, String>> entry : src.entrySet()) {
       dest.put(entry.getKey(), stringMapClone(entry.getValue()));
     }
@@ -1042,7 +1082,7 @@ public final class SliderUtils {
   }
 
   public static Map<String, String> stringMapClone(Map<String, String> src) {
-    Map<String, String> dest =  new HashMap<String, String>();
+    Map<String, String> dest = new HashMap<String, String>();
     return mergeEntries(dest, src.entrySet());
   }
 
@@ -1146,12 +1186,12 @@ public final class SliderUtils {
    * @return a classpath
    */
   public static ClasspathConstructor buildClasspath(String sliderConfDir,
-                                                    String libdir,
-                                                    Configuration config,
-                                                    boolean 
usingMiniMRCluster) {
+      String libdir,
+      Configuration config,
+      boolean usingMiniMRCluster) {
 
     ClasspathConstructor classpath = new ClasspathConstructor();
-    
+
     // add the runtime classpath needed for tests to work
     if (usingMiniMRCluster) {
       // for mini cluster we pass down the java CP properties
@@ -1175,17 +1215,18 @@ public final class SliderUtils {
    * @param errorlog log for output on an error
    * @throws FileNotFoundException if it is not a directory
    */
-  public static void verifyIsDir(File dir, Logger errorlog) throws 
FileNotFoundException {
+  public static void verifyIsDir(File dir, Logger errorlog) throws
+      FileNotFoundException {
     if (!dir.exists()) {
       errorlog.warn("contents of {}: {}", dir,
-                    listDir(dir.getParentFile()));
+          listDir(dir.getParentFile()));
       throw new FileNotFoundException(dir.toString());
     }
     if (!dir.isDirectory()) {
       errorlog.info("contents of {}: {}", dir,
-                    listDir(dir.getParentFile()));
+          listDir(dir.getParentFile()));
       throw new FileNotFoundException(
-        "Not a directory: " + dir);
+          "Not a directory: " + dir);
     }
   }
 
@@ -1195,10 +1236,11 @@ public final class SliderUtils {
    * @param errorlog log for output on an error
    * @throws FileNotFoundException
    */
-  public static void verifyFileExists(File file, Logger errorlog) throws 
FileNotFoundException {
+  public static void verifyFileExists(File file, Logger errorlog) throws
+      FileNotFoundException {
     if (!file.exists()) {
       errorlog.warn("contents of {}: {}", file,
-                    listDir(file.getParentFile()));
+          listDir(file.getParentFile()));
       throw new FileNotFoundException(file.toString());
     }
     if (!file.isFile()) {
@@ -1214,15 +1256,15 @@ public final class SliderUtils {
    * @throws BadConfigException if the key is missing
    */
   public static String verifyOptionSet(Configuration configuration, String key,
-                                       boolean allowEmpty) throws 
BadConfigException {
+      boolean allowEmpty) throws BadConfigException {
     String val = configuration.get(key);
     if (val == null) {
       throw new BadConfigException(
-        "Required configuration option \"%s\" not defined ", key);
+          "Required configuration option \"%s\" not defined ", key);
     }
     if (!allowEmpty && val.isEmpty()) {
       throw new BadConfigException(
-        "Configuration option \"%s\" must not be empty", key);
+          "Configuration option \"%s\" must not be empty", key);
     }
     return val;
   }
@@ -1235,24 +1277,25 @@ public final class SliderUtils {
    * @return the file referenced
    * @throws BadConfigException on a failure
    */
-  public static File verifyKeytabExists(Configuration siteConf, String prop) 
throws
-                                                                      
BadConfigException {
+  public static File verifyKeytabExists(Configuration siteConf,
+      String prop) throws
+      BadConfigException {
     String keytab = siteConf.get(prop);
     if (keytab == null) {
       throw new BadConfigException("Missing keytab property %s",
-                                   prop);
+          prop);
 
     }
     File keytabFile = new File(keytab);
     if (!keytabFile.exists()) {
       throw new BadConfigException("Missing keytab file %s defined in %s",
-                                   keytabFile,
-                                   prop);
+          keytabFile,
+          prop);
     }
     if (keytabFile.length() == 0 || !keytabFile.isFile()) {
       throw new BadConfigException("Invalid keytab file %s defined in %s",
-                                   keytabFile,
-                                   prop);
+          keytabFile,
+          prop);
     }
     return keytabFile;
   }
@@ -1278,12 +1321,12 @@ public final class SliderUtils {
 
     Properties props = SliderVersionInfo.loadVersionProperties();
     info.put(prefix + "." + SliderVersionInfo.APP_BUILD_INFO, 
props.getProperty(
-      SliderVersionInfo.APP_BUILD_INFO));
+        SliderVersionInfo.APP_BUILD_INFO));
     info.put(prefix + "." + SliderVersionInfo.HADOOP_BUILD_INFO,
-             props.getProperty(SliderVersionInfo.HADOOP_BUILD_INFO));
+        props.getProperty(SliderVersionInfo.HADOOP_BUILD_INFO));
 
     info.put(prefix + "." + SliderVersionInfo.HADOOP_DEPLOYED_INFO,
-             VersionInfo.getBranch() + " @" + VersionInfo.getSrcChecksum());
+        VersionInfo.getBranch() + " @" + VersionInfo.getSrcChecksum());
   }
 
   /**
@@ -1295,30 +1338,33 @@ public final class SliderUtils {
    * @param time timestamp
    */
   public static void setInfoTime(Map info,
-                                 String keyHumanTime,
-                          String keyMachineTime,
-                          long time) {
+      String keyHumanTime,
+      String keyMachineTime,
+      long time) {
     info.put(keyHumanTime, SliderUtils.toGMTString(time));
     info.put(keyMachineTime, Long.toString(time));
   }
 
-  public static Path extractImagePath(CoreFileSystem fs,  MapOperations 
internalOptions) throws
+  public static Path extractImagePath(CoreFileSystem fs,
+      MapOperations internalOptions) throws
       SliderException, IOException {
     Path imagePath;
     String imagePathOption =
         internalOptions.get(InternalKeys.INTERNAL_APPLICATION_IMAGE_PATH);
-    String appHomeOption = 
internalOptions.get(InternalKeys.INTERNAL_APPLICATION_HOME);
+    String appHomeOption =
+        internalOptions.get(InternalKeys.INTERNAL_APPLICATION_HOME);
     if (!isUnset(imagePathOption)) {
       imagePath = fs.createPathThatMustExist(imagePathOption);
     } else {
       imagePath = null;
       if (isUnset(appHomeOption)) {
-        throw new 
BadClusterStateException(ErrorStrings.E_NO_IMAGE_OR_HOME_DIR_SPECIFIED);
+        throw new BadClusterStateException(
+            ErrorStrings.E_NO_IMAGE_OR_HOME_DIR_SPECIFIED);
       }
     }
     return imagePath;
   }
-  
+
   /**
    * trigger a  JVM halt with no clean shutdown at all
    * @param status status code for exit
@@ -1373,7 +1419,7 @@ public final class SliderUtils {
    * @param paths subpaths
    * @return base+"/"+paths[0]+"/"+paths[1]...
    */
-  public static String appendToURL(String base, String...paths) {
+  public static String appendToURL(String base, String... paths) {
     String result = base;
     for (String path : paths) {
       result = appendToURL(result, path);
@@ -1390,19 +1436,19 @@ public final class SliderUtils {
    * @return
    */
   public static String truncate(String toTruncate, int maxSize) {
-    if(toTruncate == null || maxSize < 1
-       || toTruncate.length() <= maxSize) {
+    if (toTruncate == null || maxSize < 1
+        || toTruncate.length() <= maxSize) {
       return toTruncate;
     }
 
     String pad = "...";
-    if(maxSize < 10) {
+    if (maxSize < 10) {
       pad = "";
     }
     return toTruncate.substring(0, maxSize - pad.length()).concat(pad);
   }
-  
-  
+
+
   /**
    * Callable for async/scheduled halt
    */
@@ -1436,11 +1482,15 @@ public final class SliderUtils {
    */
   public static int compareTo(long left, long right) {
     long diff = left - right;
-    if (diff < 0) return -1;
-    if (diff > 0) return 1;
+    if (diff < 0) {
+      return -1;
+    }
+    if (diff > 0) {
+      return 1;
+    }
     return 0;
   }
-  
+
   /**
    * This wrapps ApplicationReports and generates a string version
    * iff the toString() operator is invoked
@@ -1459,8 +1509,8 @@ public final class SliderUtils {
   }
 
   public static InputStream getApplicationResourceInputStream(FileSystem fs,
-                                                              Path appPath,
-                                                              String entry)
+      Path appPath,
+      String entry)
       throws IOException {
     InputStream is = null;
     FSDataInputStream appStream = null;
@@ -1473,7 +1523,8 @@ public final class SliderUtils {
         if (entry.equals(zipEntry.getName())) {
           int size = (int) zipEntry.getSize();
           if (size != -1) {
-            log.info("Reading {} of size {}", zipEntry.getName(), 
zipEntry.getSize());
+            log.info("Reading {} of size {}", zipEntry.getName(),
+                zipEntry.getSize());
             byte[] content = new byte[size];
             int offset = 0;
             while (offset < size) {
@@ -1509,42 +1560,218 @@ public final class SliderUtils {
    * the headers. 
    * @throws IOException on any problem reading the file
    * @throws FileNotFoundException if the file is not considered valid
+   * @param logger
    */
-  public static void verifyWinUtilsValid() throws IOException {
+  public static void maybeVerifyWinUtilsValid(Logger logger) throws 
IOException, SliderException {
     if (!Shell.WINDOWS) {
       return;
     }
-    String winUtilsPath = Shell.getWinUtilsPath();
-    if (winUtilsPath == null) {
-      throw new FileNotFoundException(WINUTILS + " not found on Path : " +
+    String exePath = Shell.getWinUtilsPath();
+    String program = WINUTILS;
+    if (exePath == null) {
+      throw new FileNotFoundException(program + " not found on Path : " +
                                       System.getenv("Path"));
     }
-    File winUtils = new File(winUtilsPath);
-    if (!winUtils.isFile()) {
-      throw new FileNotFoundException(WINUTILS
-                  + " at " + winUtilsPath
-                  + " is not a file");
+    File exe = new File(exePath);
+
+    verifyWindowsExe(program, exe);
+    execCommand(WINUTILS, 0, 5000, log, null, exePath, "systeminfo");
+
+  }
+
+  protected static void verifyIsFile(String program, File exe) throws
+      FileNotFoundException {
+    if (!exe.isFile()) {
+      throw new FileNotFoundException(program
+                                      + " at " + exe
+                                      + " is not a file");
 
     }
-    if (winUtils.length() < 0x100) {
-      throw new FileNotFoundException(WINUTILS
-                    + " at " + winUtilsPath
-                    + " is too short to be an executable");
+  }
+
+  protected static void verifyFileSize(String program,
+      File exe,
+      int minFileSize) throws FileNotFoundException {
+    if (exe.length() < minFileSize) {
+      throw new FileNotFoundException(program
+                                      + " at " + exe
+                                      + " is too short to be an executable");
     }
+  }
+
+  /**
+   * Look for the windows executable and check it has the right headers.
+   * <code>File.canRead()</code> doesn't work on windows, so the reading
+   * is mandatory.
+   * 
+   * @param program program name for errors
+   * @param exe executable
+   * @throws IOException IOE
+   */
+  public static void verifyWindowsExe(String program, File exe) 
+      throws IOException {
+    verifyIsFile(program, exe);
+
+    verifyFileSize(program, exe, 0x100);
+
     // now read two bytes and verify the header.
+    
     FileReader reader = null;
     try {
       int[] header = new int[2];
-      reader = new FileReader(winUtilsPath);
+      reader = new FileReader(exe);
       header[0] = reader.read();
       header[1] = reader.read();
-      if (header[0] != 'M' || header[1] != 'Z') {
-        throw new FileNotFoundException(WINUTILS
-                  + " at " + winUtilsPath
-                  + " is not a windows executable file");
+      if ((header[0] != 'M' || header[1] != 'Z')) {
+        throw new FileNotFoundException(program
+                                        + " at " + exe
+                                        + " is not a windows executable file");
       }
     } finally {
       IOUtils.closeStream(reader);
     }
   }
+
+  /**
+   * Verify that a Unix exe works
+   * @param program program name for errors
+   * @param exe executable
+   * @throws IOException IOE
+
+   */
+  public static void verifyUnixExe(String program, File exe)
+      throws IOException {
+    verifyIsFile(program, exe);
+
+    // read flag
+    if (!exe.canRead()) {
+      throw new IOException("Cannot read " + program + " at " + exe);
+    }
+    // exe flag
+    if (!exe.canExecute()) {
+      throw new IOException("Cannot execute " + program + " at " + exe);
+    }
+  }
+
+  /**
+   * Validate an executable
+   * @param program
+   * @param exe
+   * @throws IOException
+   */
+  public static void validateExe(String program, File exe) throws IOException {
+    if (!Shell.WINDOWS) {
+      verifyWindowsExe(program, exe);
+    } else {
+      verifyUnixExe(program, exe);
+    }
+  }
+
+
+  /**
+   * Execute a command for a test operation
+   * @param name name in error
+   * @param status status code expected
+   * @param timeoutMillis timeout in millis for process to finish
+   * @param logger
+   *@param outputString optional string to grep for (must not span a line)
+   * @param commands commands   @return the process
+   * @throws IOException on any failure.
+   */
+  public static ForkedProcessService execCommand(String name,
+      int status,
+      long timeoutMillis,
+      Logger logger,
+      String outputString,
+      String... commands) throws IOException, SliderException {
+    Preconditions.checkArgument(isSet(name), "no name");
+    Preconditions.checkArgument(commands.length > 0, "no commands");
+    Preconditions.checkArgument(isSet(commands[0]), "empty command");
+
+    ForkedProcessService process;
+
+
+    process = new ForkedProcessService(
+        name,
+        new HashMap<String, String>(),
+        Arrays.asList(commands));
+    process.setProcessLog(logger);
+    process.init(new Configuration());
+    String errorText = null;
+    EndOfServiceWaiter waiter = new EndOfServiceWaiter(process);
+    process.start();
+    try {
+      waiter.waitForServiceToStop(timeoutMillis);
+      int exitCode = process.getExitCode();
+      List<String> recentOutput = process.getRecentOutput();
+      if (status != exitCode) {
+        // error condition
+        errorText = "Expected exit code={" + status + "}, "
+                    + "actual exit code={" + exitCode + "}";
+      } else {
+        if (isSet(outputString)) {
+          boolean found = false;
+          for (String line : recentOutput) {
+            if (line.contains(outputString)) {
+              found = true;
+              break;
+            }
+          }
+          if (!found) {
+            errorText = "Did not find \"" + outputString + "\""
+                        + " in output";
+          }
+        }
+      }
+      if (errorText== null) {
+        return process;
+      }
+
+    } catch (InterruptedException e) {
+      throw new InterruptedIOException(e.toString());
+    } catch (TimeoutException e) {
+      log.debug("");
+      errorText = e.toString();
+    }
+    // error text: non null ==> operation failed
+    log.warn(errorText);
+    List<String> recentOutput = process.getRecentOutput();
+    for (String line : recentOutput) {
+      log.info(line);
+    }
+    throw new SliderException(LauncherExitCodes.EXIT_OTHER_FAILURE,
+        "Process %s failed: %s", name, errorText);
+
+  }
+
+
+  /**
+   * Validate the slider client-side execution environment.
+   * This looks for everything felt to be critical for execution, including
+   * native binaries and other essential dependencies.
+   * @param logger logger to log to on normal execution
+   * @throws IOException on IO failures
+   * @throws SliderException on validation failures
+   */
+  public static void validateSliderClientEnvironment(Logger logger) throws
+      IOException,
+      SliderException {
+    maybeVerifyWinUtilsValid(logger);
+  }
+
+  /**
+   * Validate the slider server-side execution environment.
+   * This looks for everything felt to be critical for execution, including
+   * native binaries and other essential dependencies.
+   * @param logger logger to log to on normal execution
+   * @throws IOException on IO failures
+   * @throws SliderException on validation failures
+   */
+  public static void validateSliderServerEnvironment(Logger logger) throws
+      IOException,
+      SliderException {
+    maybeVerifyWinUtilsValid(logger);
+    execCommand(OPENSSL, 0, 5000, logger, "OpenSSL", OPENSSL, "version");
+    execCommand(PYTHON, 0, 5000, logger, "Python", PYTHON, "--version");
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/0f67b104/slider-core/src/main/java/org/apache/slider/server/services/utility/EndOfServiceWaiter.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/server/services/utility/EndOfServiceWaiter.java
 
b/slider-core/src/main/java/org/apache/slider/server/services/utility/EndOfServiceWaiter.java
new file mode 100644
index 0000000..6e8add1
--- /dev/null
+++ 
b/slider-core/src/main/java/org/apache/slider/server/services/utility/EndOfServiceWaiter.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.slider.server.services.utility;
+
+import org.apache.hadoop.service.Service;
+import org.apache.hadoop.service.ServiceStateChangeListener;
+
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Wait for a service to stop.
+ * 
+ * WARNING: the notification may come in as soon as the service enters
+ * the stopped state: it may take some time for the actual stop operation
+ * to complete.
+ */
+public class EndOfServiceWaiter implements ServiceStateChangeListener {
+
+  private final AtomicBoolean finished = new AtomicBoolean(false);
+  private final String name;
+
+  /**
+   * Wait for a service; use the service name as this instance's name
+   * @param service service
+   */
+  public EndOfServiceWaiter(Service service) {
+    this(service.getName(), service);
+  }
+
+
+  /**
+   * Wait for a service
+   * @param name name for messages
+   * @param service service
+   */
+  public EndOfServiceWaiter(String name, Service service) {
+    this.name = name;
+    service.registerServiceListener(this);
+  }
+
+  public synchronized void waitForServiceToStop(long timeout) throws
+      InterruptedException, TimeoutException {
+    if (!finished.get()) {
+      wait(timeout);
+    }
+    if (!finished.get()) {
+      throw new TimeoutException(name
+               + " did not finish after " + timeout +
+               " milliseconds");
+    }
+  }
+
+  /**
+   * Wait for service state change callbacks; notify self if the service has
+   * now stopped
+   * @param service service
+   */
+  @Override
+  public synchronized void stateChanged(Service service) {
+    if (service.isInState(Service.STATE.STOPPED)) {
+      finished.set(true);
+      notify();
+    }
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/0f67b104/slider-core/src/main/java/org/apache/slider/server/services/workflow/ForkedProcessService.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/server/services/workflow/ForkedProcessService.java
 
b/slider-core/src/main/java/org/apache/slider/server/services/workflow/ForkedProcessService.java
index 46c724c..b801993 100644
--- 
a/slider-core/src/main/java/org/apache/slider/server/services/workflow/ForkedProcessService.java
+++ 
b/slider-core/src/main/java/org/apache/slider/server/services/workflow/ForkedProcessService.java
@@ -81,7 +81,11 @@ public class ForkedProcessService
   private LongLivedProcess process;
   private int executionTimeout = -1;
   private int timeoutCode = 1;
-
+  /** 
+  log to log to; defaults to this service log
+   */
+  private Logger processLog = LOG;
+  
   /**
    * Exit code set when the spawned process exits
    */
@@ -102,7 +106,8 @@ public class ForkedProcessService
    * @param env environment variables above those generated by
    * @throws IOException IO problems
    */
-  public ForkedProcessService(String name, Map<String, String> env,
+  public ForkedProcessService(String name,
+      Map<String, String> env,
       List<String> commandList) throws IOException {
     super(name);
     build(env, commandList);
@@ -130,6 +135,15 @@ public class ForkedProcessService
   }
 
   /**
+   * Set the process log. This may be null for "do not log"
+   * @param processLog process log
+   */
+  public void setProcessLog(Logger processLog) {
+    this.processLog = processLog;
+    process.setProcessLog(processLog);
+  }
+
+  /**
    * Set the timeout by which time a process must have finished -or -1 for 
forever
    * @param timeout timeout in milliseconds
    */
@@ -148,7 +162,8 @@ public class ForkedProcessService
                     List<String> commandList)
       throws IOException {
     assert process == null;
-    process = new LongLivedProcess(getName(), LOG, commandList);
+
+    process = new LongLivedProcess(getName(), processLog, commandList);
     process.setLifecycleCallback(this);
     //set the env variable mapping
     process.putEnvMap(env);

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/0f67b104/slider-core/src/main/java/org/apache/slider/server/services/workflow/LongLivedProcess.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/server/services/workflow/LongLivedProcess.java
 
b/slider-core/src/main/java/org/apache/slider/server/services/workflow/LongLivedProcess.java
index c8ff758..6f83811 100644
--- 
a/slider-core/src/main/java/org/apache/slider/server/services/workflow/LongLivedProcess.java
+++ 
b/slider-core/src/main/java/org/apache/slider/server/services/workflow/LongLivedProcess.java
@@ -90,7 +90,7 @@ public class LongLivedProcess implements Runnable {
    * Log supplied in the constructor for the spawned process -accessible
    * to inner classes
    */
-  private final Logger processLog;
+  private Logger processLog;
   
   /**
    * Class log -accessible to inner classes
@@ -102,10 +102,15 @@ public class LongLivedProcess implements Runnable {
    */
   private final AtomicBoolean finished = new AtomicBoolean(false);
 
+  /**
+   * Create an instance
+   * @param name process name
+   * @param processLog log for output (or null)
+   * @param commands command list
+   */
   public LongLivedProcess(String name,
       Logger processLog,
       List<String> commands) {
-    Preconditions.checkArgument(processLog != null, "processLog");
     Preconditions.checkArgument(commands != null, "commands");
 
     this.name = name;
@@ -168,6 +173,14 @@ public class LongLivedProcess implements Runnable {
   }
 
   /**
+   * Set the process log. Ignored once the process starts
+   * @param processLog new log ... may be null
+   */
+  public void setProcessLog(Logger processLog) {
+    this.processLog = processLog;
+  }
+
+  /**
    * Get the process reference
    * @return the process -null if the process is  not started
    */
@@ -398,10 +411,11 @@ public class LongLivedProcess implements Runnable {
    * something that is only called once per line of IO?
    * @param line line to record
    * @param isErrorStream is the line from the error stream
-   * @param logger logger to log to
+   * @param logger logger to log to - null for no logging
    */
   private synchronized void recordRecentLine(String line,
-      boolean isErrorStream, Logger logger) {
+      boolean isErrorStream,
+      Logger logger) {
     if (line == null) {
       return;
     }
@@ -410,10 +424,12 @@ public class LongLivedProcess implements Runnable {
     if (recentLines.size() > recentLineLimit) {
       recentLines.remove(0);
     }
-    if (isErrorStream) {
-      logger.warn(line);
-    } else {
-      logger.info(line);
+    if (logger != null) {
+      if (isErrorStream) {
+        logger.warn(line);
+      } else {
+        logger.info(line);
+      }
     }
   }
 
@@ -428,6 +444,12 @@ public class LongLivedProcess implements Runnable {
     private final Logger streamLog;
     private final int sleepTime;
 
+    /**
+     * Create an instance
+     * @param streamLog log -or null to disable logging (recent entries
+     * will still be retained)
+     * @param sleepTime time to sleep when stopping
+     */
     private ProcessStreamReader(Logger streamLog, int sleepTime) {
       this.streamLog = streamLog;
       this.sleepTime = sleepTime;

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/0f67b104/slider-core/src/test/groovy/org/apache/slider/common/tools/TestExecutionEnvironment.groovy
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/groovy/org/apache/slider/common/tools/TestExecutionEnvironment.groovy
 
b/slider-core/src/test/groovy/org/apache/slider/common/tools/TestExecutionEnvironment.groovy
new file mode 100644
index 0000000..5489366
--- /dev/null
+++ 
b/slider-core/src/test/groovy/org/apache/slider/common/tools/TestExecutionEnvironment.groovy
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.slider.common.tools
+
+import groovy.transform.CompileStatic
+import groovy.util.logging.Slf4j
+import org.apache.slider.test.SliderTestBase
+import org.junit.Test
+
+@CompileStatic
+@Slf4j
+class TestExecutionEnvironment extends SliderTestBase {
+  
+  @Test
+  public void testClientEnv() throws Throwable {
+    SliderUtils.validateSliderClientEnvironment(log)
+  }
+  
+  
+  @Test
+  public void testServerEnv() throws Throwable {
+    SliderUtils.validateSliderServerEnvironment(log)
+  }
+  
+  
+}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/0f67b104/slider-core/src/test/groovy/org/apache/slider/common/tools/TestWindowsSupport.groovy
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/groovy/org/apache/slider/common/tools/TestWindowsSupport.groovy
 
b/slider-core/src/test/groovy/org/apache/slider/common/tools/TestWindowsSupport.groovy
index 6ae683a..98fa183 100644
--- 
a/slider-core/src/test/groovy/org/apache/slider/common/tools/TestWindowsSupport.groovy
+++ 
b/slider-core/src/test/groovy/org/apache/slider/common/tools/TestWindowsSupport.groovy
@@ -27,7 +27,7 @@ import org.apache.hadoop.fs.Path
 import org.apache.hadoop.fs.FileSystem as HadoopFS
 import org.apache.hadoop.util.Shell
 import org.apache.slider.providers.agent.AgentUtils
-import org.apache.slider.server.services.workflow.EndOfServiceWaiter
+import org.apache.slider.server.services.utility.EndOfServiceWaiter
 import org.apache.slider.server.services.workflow.ForkedProcessService
 import org.apache.slider.test.SliderTestBase
 import org.junit.Test
@@ -134,7 +134,7 @@ class TestWindowsSupport extends SliderTestBase {
   @Test
   public void testHasWinutils() throws Throwable {
     assume(Shell.WINDOWS, "not windows")
-    SliderUtils.verifyWinUtilsValid()
+    SliderUtils.maybeVerifyWinUtilsValid(log)
   }
 
   @Test

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/0f67b104/slider-core/src/test/java/org/apache/slider/server/services/workflow/EndOfServiceWaiter.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/java/org/apache/slider/server/services/workflow/EndOfServiceWaiter.java
 
b/slider-core/src/test/java/org/apache/slider/server/services/workflow/EndOfServiceWaiter.java
deleted file mode 100644
index 5e6df3b..0000000
--- 
a/slider-core/src/test/java/org/apache/slider/server/services/workflow/EndOfServiceWaiter.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.slider.server.services.workflow;
-
-import org.apache.hadoop.service.Service;
-import org.apache.hadoop.service.ServiceStateChangeListener;
-import org.junit.Assert;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Wait for a service to stop
- */
-public class EndOfServiceWaiter implements ServiceStateChangeListener {
-
-  private final AtomicBoolean finished = new AtomicBoolean(false);
-
-  public EndOfServiceWaiter(Service svc) {
-    svc.registerServiceListener(this);
-  }
-
-  public synchronized void waitForServiceToStop(long timeout) throws
-      InterruptedException {
-    if (!finished.get()) {
-      wait(timeout);
-    }
-    Assert.assertTrue("Service did not finish in time period",
-        finished.get());
-  }
-
-  @Override
-  public synchronized void stateChanged(Service service) {
-    if (service.isInState(Service.STATE.STOPPED)) {
-      finished.set(true);
-      notify();
-    }
-  }
-
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/0f67b104/slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowForkedProcessService.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowForkedProcessService.java
 
b/slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowForkedProcessService.java
index 6d08156..e8f7d88 100644
--- 
a/slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowForkedProcessService.java
+++ 
b/slider-core/src/test/java/org/apache/slider/server/services/workflow/TestWorkflowForkedProcessService.java
@@ -20,6 +20,7 @@ package org.apache.slider.server.services.workflow;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.service.ServiceOperations;
+import org.apache.slider.server.services.utility.EndOfServiceWaiter;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -31,6 +32,7 @@ import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Test the long lived process by executing a command that works and a command
@@ -131,7 +133,7 @@ public class TestWorkflowForkedProcessService extends 
WorkflowServiceTestBase {
     return process;
   }
 
-  public void exec() throws InterruptedException {
+  public void exec() throws InterruptedException, TimeoutException {
     assertNotNull(process);
     EndOfServiceWaiter waiter = new EndOfServiceWaiter(process);
     process.start();

Reply via email to