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();