/** * A {@link GSet} is set, which supports the {@link #get(Object)} operation. The {@link * #get(Object)} operation uses a key to lookup an element. * * <p>Null element is not supported. * * @param <K> The type of the keys. * @param <E> The type of the elements, which must be a subclass of the keys. */ @InterfaceAudience.Private public interface GSet<K, E extends K> extends Iterable<E> { static final /* LogLOG=LogFactory.getLog(GSet.class); */ UtilNamespace LOG = LoggerFactory.getLogger(UtilNamespace.class, new SimpleLogger()); /** @return The size of this set. */ int size(); /** * Does this set contain an element corresponding to the given key? * * @param key The given key. * @return true if the given key equals to a stored element. Otherwise, return false. * @throws NullPointerException if key == null. */ boolean contains(K key); /** * Return the stored element which is equal to the given key. This operation is similar to {@link * java.util.Map#get(Object)}. * * @param key The given key. * @return The stored element if it exists. Otherwise, return null. * @throws NullPointerException if key == null. */ E get(K key); /** * Add/replace an element. If the element does not exist, add it to the set. Otherwise, replace * the existing element. * * <p>Note that this operation is similar to {@link java.util.Map#put(Object, Object)} but is * different from {@link java.util.Set#add(Object)} which does not replace the existing element if * there is any. * * @param element The element being put. * @return the previous stored element if there is any. Otherwise, return null. * @throws NullPointerException if element == null. */ E put(E element); /** * Remove the element corresponding to the given key. This operation is similar to {@link * java.util.Map#remove(Object)}. * * @param key The key of the element being removed. * @return If such element exists, return it. Otherwise, return null. * @throws NullPointerException if key == null. */ E remove(K key); void clear(); }
/** Facilitates hooking process termination for tests and debugging. */ @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"}) @InterfaceStability.Unstable public final class ExitUtil { private static final /* LogLOG=LogFactory.getLog(ExitUtil.class.getName()); */ UtilNamespace LOG = LoggerFactory.getLogger(UtilNamespace.class, new SimpleLogger()); private static volatile boolean systemExitDisabled = false; private static volatile boolean systemHaltDisabled = false; private static volatile ExitException firstExitException; private static volatile HaltException firstHaltException; public static class ExitException extends RuntimeException { private static final long serialVersionUID = 1L; public final int status; public ExitException(int status, String msg) { super(msg); this.status = status; } } public static class HaltException extends RuntimeException { private static final long serialVersionUID = 1L; public final int status; public HaltException(int status, String msg) { super(msg); this.status = status; } } /** Disable the use of System.exit for testing. */ public static void disableSystemExit() { systemExitDisabled = true; } /** Disable the use of {@code Runtime.getRuntime().halt() } for testing. */ public static void disableSystemHalt() { systemHaltDisabled = true; } /** @return true if terminate has been called */ public static boolean terminateCalled() { // Either we set this member or we actually called System#exit return firstExitException != null; } /** @return true if halt has been called */ public static boolean haltCalled() { return firstHaltException != null; } /** @return the first ExitException thrown, null if none thrown yet */ public static ExitException getFirstExitException() { return firstExitException; } /** @return the first {@code HaltException} thrown, null if none thrown yet */ public static HaltException getFirstHaltException() { return firstHaltException; } /** * Reset the tracking of process termination. This is for use in unit tests where one test in the * suite expects an exit but others do not. */ public static void resetFirstExitException() { firstExitException = null; } public static void resetFirstHaltException() { firstHaltException = null; } /** * Terminate the current process. Note that terminate is the *only* method that should be used to * terminate the daemon processes. * * @param status exit code * @param msg message used to create the {@code ExitException} * @throws ExitException if System.exit is disabled for test purposes */ public static void terminate(int status, String msg) throws ExitException { /* LOG.info("Exiting with status "+status) */ LOG.exiting_with_status(status).info(); if (systemExitDisabled) { ExitException ee = new ExitException(status, msg); /* LOG.fatal("Terminate called",ee) */ LOG.terminate_called(ee.toString()).fatal(); if (null == firstExitException) { firstExitException = ee; } throw ee; } System.exit(status); } /** * Forcibly terminates the currently running Java virtual machine. * * @param status exit code * @param msg message used to create the {@code HaltException} * @throws HaltException if Runtime.getRuntime().halt() is disabled for test purposes */ public static void halt(int status, String msg) throws HaltException { /* LOG.info("Halt with status "+status+" Message: "+msg) */ LOG.halt_with_status_message(status, msg).info(); if (systemHaltDisabled) { HaltException ee = new HaltException(status, msg); /* LOG.fatal("Halt called",ee) */ LOG.halt_called(ee.toString()).fatal(); if (null == firstHaltException) { firstHaltException = ee; } throw ee; } Runtime.getRuntime().halt(status); } /** * Like {@link terminate(int, String)} but uses the given throwable to initialize the * ExitException. * * @param status * @param t throwable used to create the ExitException * @throws ExitException if System.exit is disabled for test purposes */ public static void terminate(int status, Throwable t) throws ExitException { terminate(status, StringUtils.stringifyException(t)); } /** * Forcibly terminates the currently running Java virtual machine. * * @param status * @param t * @throws ExitException */ public static void halt(int status, Throwable t) throws HaltException { halt(status, StringUtils.stringifyException(t)); } /** * Like {@link terminate(int, String)} without a message. * * @param status * @throws ExitException if System.exit is disabled for test purposes */ public static void terminate(int status) throws ExitException { terminate(status, "ExitException"); } /** * Forcibly terminates the currently running Java virtual machine. * * @param status * @throws ExitException */ public static void halt(int status) throws HaltException { halt(status, "HaltException"); } }
/** * A base class for file-based {@link InputFormat}s. * * <p><code>FileInputFormat</code> is the base class for all file-based <code>InputFormat</code>s. * This provides a generic implementation of {@link #getSplits(JobContext)}. Subclasses of <code> * FileInputFormat</code> can also override the {@link #isSplitable(JobContext, Path)} method to * ensure input-files are not split-up and are processed as a whole by {@link Mapper}s. */ @InterfaceAudience.Public @InterfaceStability.Stable public abstract class FileInputFormat<K, V> extends InputFormat<K, V> { public static final String INPUT_DIR = "mapreduce.input.fileinputformat.inputdir"; public static final String SPLIT_MAXSIZE = "mapreduce.input.fileinputformat.split.maxsize"; public static final String SPLIT_MINSIZE = "mapreduce.input.fileinputformat.split.minsize"; public static final String PATHFILTER_CLASS = "mapreduce.input.pathFilter.class"; public static final String NUM_INPUT_FILES = "mapreduce.input.fileinputformat.numinputfiles"; public static final String INPUT_DIR_RECURSIVE = "mapreduce.input.fileinputformat.input.dir.recursive"; public static final String LIST_STATUS_NUM_THREADS = "mapreduce.input.fileinputformat.list-status.num-threads"; public static final int DEFAULT_LIST_STATUS_NUM_THREADS = 1; private static final /* LogLOG=LogFactory.getLog(FileInputFormat.class); */ MapreduceNamespace LOG = LoggerFactory.getLogger(MapreduceNamespace.class, new SimpleLogger()); private static final double SPLIT_SLOP = 1.1; // 10% slop @Deprecated public static enum Counter { BYTES_READ } private static final PathFilter hiddenFileFilter = new PathFilter() { public boolean accept(Path p) { String name = p.getName(); return !name.startsWith("_") && !name.startsWith("."); } }; /** * Proxy PathFilter that accepts a path only if all filters given in the constructor do. Used by * the listPaths() to apply the built-in hiddenFileFilter together with a user provided one (if * any). */ private static class MultiPathFilter implements PathFilter { private List<PathFilter> filters; public MultiPathFilter(List<PathFilter> filters) { this.filters = filters; } public boolean accept(Path path) { for (PathFilter filter : filters) { if (!filter.accept(path)) { return false; } } return true; } } /** * @param job the job to modify * @param inputDirRecursive */ public static void setInputDirRecursive(Job job, boolean inputDirRecursive) { job.getConfiguration().setBoolean(INPUT_DIR_RECURSIVE, inputDirRecursive); } /** * @param job the job to look at. * @return should the files to be read recursively? */ public static boolean getInputDirRecursive(JobContext job) { return job.getConfiguration().getBoolean(INPUT_DIR_RECURSIVE, false); } /** * Get the lower bound on split size imposed by the format. * * @return the number of bytes of the minimal split for this format */ protected long getFormatMinSplitSize() { return 1; } /** * Is the given filename splitable? Usually, true, but if the file is stream compressed, it will * not be. <code>FileInputFormat</code> implementations can override this and return <code>false * </code> to ensure that individual input files are never split-up so that {@link Mapper}s * process entire files. * * @param context the job context * @param filename the file name to check * @return is this file splitable? */ protected boolean isSplitable(JobContext context, Path filename) { return true; } /** * Set a PathFilter to be applied to the input paths for the map-reduce job. * * @param job the job to modify * @param filter the PathFilter class use for filtering the input paths. */ public static void setInputPathFilter(Job job, Class<? extends PathFilter> filter) { job.getConfiguration().setClass(PATHFILTER_CLASS, filter, PathFilter.class); } /** * Set the minimum input split size * * @param job the job to modify * @param size the minimum size */ public static void setMinInputSplitSize(Job job, long size) { job.getConfiguration().setLong(SPLIT_MINSIZE, size); } /** * Get the minimum split size * * @param job the job * @return the minimum number of bytes that can be in a split */ public static long getMinSplitSize(JobContext job) { return job.getConfiguration().getLong(SPLIT_MINSIZE, 1L); } /** * Set the maximum split size * * @param job the job to modify * @param size the maximum split size */ public static void setMaxInputSplitSize(Job job, long size) { job.getConfiguration().setLong(SPLIT_MAXSIZE, size); } /** * Get the maximum split size. * * @param context the job to look at. * @return the maximum number of bytes a split can include */ public static long getMaxSplitSize(JobContext context) { return context.getConfiguration().getLong(SPLIT_MAXSIZE, Long.MAX_VALUE); } /** * Get a PathFilter instance of the filter set for the input paths. * * @return the PathFilter instance set for the job, NULL if none has been set. */ public static PathFilter getInputPathFilter(JobContext context) { Configuration conf = context.getConfiguration(); Class<?> filterClass = conf.getClass(PATHFILTER_CLASS, null, PathFilter.class); return (filterClass != null) ? (PathFilter) ReflectionUtils.newInstance(filterClass, conf) : null; } /** * List input directories. Subclasses may override to, e.g., select only files matching a regular * expression. * * @param job the job to list input paths for * @return array of FileStatus objects * @throws IOException if zero items. */ protected List<FileStatus> listStatus(JobContext job) throws IOException { Path[] dirs = getInputPaths(job); if (dirs.length == 0) { throw new IOException("No input paths specified in job"); } // get tokens for all the required FileSystems.. TokenCache.obtainTokensForNamenodes(job.getCredentials(), dirs, job.getConfiguration()); // Whether we need to recursive look into the directory structure boolean recursive = getInputDirRecursive(job); // creates a MultiPathFilter with the hiddenFileFilter and the // user provided one (if any). List<PathFilter> filters = new ArrayList<PathFilter>(); filters.add(hiddenFileFilter); PathFilter jobFilter = getInputPathFilter(job); if (jobFilter != null) { filters.add(jobFilter); } PathFilter inputFilter = new MultiPathFilter(filters); List<FileStatus> result = null; int numThreads = job.getConfiguration().getInt(LIST_STATUS_NUM_THREADS, DEFAULT_LIST_STATUS_NUM_THREADS); Stopwatch sw = new Stopwatch().start(); if (numThreads == 1) { result = singleThreadedListStatus(job, dirs, inputFilter, recursive); } else { Iterable<FileStatus> locatedFiles = null; try { LocatedFileStatusFetcher locatedFileStatusFetcher = new LocatedFileStatusFetcher( job.getConfiguration(), dirs, recursive, inputFilter, true); locatedFiles = locatedFileStatusFetcher.getFileStatuses(); } catch (InterruptedException e) { throw new IOException("Interrupted while getting file statuses"); } result = Lists.newArrayList(locatedFiles); } sw.stop(); if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Time taken to get FileStatuses: "+sw.elapsedMillis()) */ LOG.time_taken_get_filestatuses(String.valueOf(sw.elapsedMillis())).tag("methodCall").debug(); } /* LOG.info("Total input paths to process : "+result.size()) */ LOG.total_input_paths_process(String.valueOf(result.size())).tag("methodCall").info(); return result; } private List<FileStatus> singleThreadedListStatus( JobContext job, Path[] dirs, PathFilter inputFilter, boolean recursive) throws IOException { List<FileStatus> result = new ArrayList<FileStatus>(); List<IOException> errors = new ArrayList<IOException>(); for (int i = 0; i < dirs.length; ++i) { Path p = dirs[i]; FileSystem fs = p.getFileSystem(job.getConfiguration()); FileStatus[] matches = fs.globStatus(p, inputFilter); if (matches == null) { errors.add(new IOException("Input path does not exist: " + p)); } else if (matches.length == 0) { errors.add(new IOException("Input Pattern " + p + " matches 0 files")); } else { for (FileStatus globStat : matches) { if (globStat.isDirectory()) { RemoteIterator<LocatedFileStatus> iter = fs.listLocatedStatus(globStat.getPath()); while (iter.hasNext()) { LocatedFileStatus stat = iter.next(); if (inputFilter.accept(stat.getPath())) { if (recursive && stat.isDirectory()) { addInputPathRecursively(result, fs, stat.getPath(), inputFilter); } else { result.add(stat); } } } } else { result.add(globStat); } } } } if (!errors.isEmpty()) { throw new InvalidInputException(errors); } return result; } /** * Add files in the input path recursively into the results. * * @param result The List to store all files. * @param fs The FileSystem. * @param path The input path. * @param inputFilter The input filter that can be used to filter files/dirs. * @throws IOException */ protected void addInputPathRecursively( List<FileStatus> result, FileSystem fs, Path path, PathFilter inputFilter) throws IOException { RemoteIterator<LocatedFileStatus> iter = fs.listLocatedStatus(path); while (iter.hasNext()) { LocatedFileStatus stat = iter.next(); if (inputFilter.accept(stat.getPath())) { if (stat.isDirectory()) { addInputPathRecursively(result, fs, stat.getPath(), inputFilter); } else { result.add(stat); } } } } /** * A factory that makes the split for this class. It can be overridden by sub-classes to make * sub-types */ protected FileSplit makeSplit(Path file, long start, long length, String[] hosts) { return new FileSplit(file, start, length, hosts); } /** * Generate the list of files and make them into FileSplits. * * @param job the job context * @throws IOException */ public List<InputSplit> getSplits(JobContext job) throws IOException { Stopwatch sw = new Stopwatch().start(); long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); long maxSize = getMaxSplitSize(job); // generate splits List<InputSplit> splits = new ArrayList<InputSplit>(); List<FileStatus> files = listStatus(job); for (FileStatus file : files) { Path path = file.getPath(); long length = file.getLen(); if (length != 0) { BlockLocation[] blkLocations; if (file instanceof LocatedFileStatus) { blkLocations = ((LocatedFileStatus) file).getBlockLocations(); } else { FileSystem fs = path.getFileSystem(job.getConfiguration()); blkLocations = fs.getFileBlockLocations(file, 0, length); } if (isSplitable(job, path)) { long blockSize = file.getBlockSize(); long splitSize = computeSplitSize(blockSize, minSize, maxSize); long bytesRemaining = length; while (((double) bytesRemaining) / splitSize > SPLIT_SLOP) { int blkIndex = getBlockIndex(blkLocations, length - bytesRemaining); splits.add( makeSplit( path, length - bytesRemaining, splitSize, blkLocations[blkIndex].getHosts())); bytesRemaining -= splitSize; } if (bytesRemaining != 0) { int blkIndex = getBlockIndex(blkLocations, length - bytesRemaining); splits.add( makeSplit( path, length - bytesRemaining, bytesRemaining, blkLocations[blkIndex].getHosts())); } } else { // not splitable splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts())); } } else { // Create empty hosts array for zero length files splits.add(makeSplit(path, 0, length, new String[0])); } } // Save the number of input files for metrics/loadgen job.getConfiguration().setLong(NUM_INPUT_FILES, files.size()); sw.stop(); if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Total # of splits generated by getSplits: "+splits.size()+", TimeTaken: "+sw.elapsedMillis()) */ LOG.total_splits_generated_getsplits_timetak( String.valueOf(splits.size()), String.valueOf(sw.elapsedMillis())) .tag("methodCall") .debug(); } return splits; } protected long computeSplitSize(long blockSize, long minSize, long maxSize) { return Math.max(minSize, Math.min(maxSize, blockSize)); } protected int getBlockIndex(BlockLocation[] blkLocations, long offset) { for (int i = 0; i < blkLocations.length; i++) { // is the offset inside this block? if ((blkLocations[i].getOffset() <= offset) && (offset < blkLocations[i].getOffset() + blkLocations[i].getLength())) { return i; } } BlockLocation last = blkLocations[blkLocations.length - 1]; long fileLength = last.getOffset() + last.getLength() - 1; throw new IllegalArgumentException( "Offset " + offset + " is outside of file (0.." + fileLength + ")"); } /** * Sets the given comma separated paths as the list of inputs for the map-reduce job. * * @param job the job * @param commaSeparatedPaths Comma separated paths to be set as the list of inputs for the * map-reduce job. */ public static void setInputPaths(Job job, String commaSeparatedPaths) throws IOException { setInputPaths(job, StringUtils.stringToPath(getPathStrings(commaSeparatedPaths))); } /** * Add the given comma separated paths to the list of inputs for the map-reduce job. * * @param job The job to modify * @param commaSeparatedPaths Comma separated paths to be added to the list of inputs for the * map-reduce job. */ public static void addInputPaths(Job job, String commaSeparatedPaths) throws IOException { for (String str : getPathStrings(commaSeparatedPaths)) { addInputPath(job, new Path(str)); } } /** * Set the array of {@link Path}s as the list of inputs for the map-reduce job. * * @param job The job to modify * @param inputPaths the {@link Path}s of the input directories/files for the map-reduce job. */ public static void setInputPaths(Job job, Path... inputPaths) throws IOException { Configuration conf = job.getConfiguration(); Path path = inputPaths[0].getFileSystem(conf).makeQualified(inputPaths[0]); StringBuffer str = new StringBuffer(StringUtils.escapeString(path.toString())); for (int i = 1; i < inputPaths.length; i++) { str.append(StringUtils.COMMA_STR); path = inputPaths[i].getFileSystem(conf).makeQualified(inputPaths[i]); str.append(StringUtils.escapeString(path.toString())); } conf.set(INPUT_DIR, str.toString()); } /** * Add a {@link Path} to the list of inputs for the map-reduce job. * * @param job The {@link Job} to modify * @param path {@link Path} to be added to the list of inputs for the map-reduce job. */ public static void addInputPath(Job job, Path path) throws IOException { Configuration conf = job.getConfiguration(); path = path.getFileSystem(conf).makeQualified(path); String dirStr = StringUtils.escapeString(path.toString()); String dirs = conf.get(INPUT_DIR); conf.set(INPUT_DIR, dirs == null ? dirStr : dirs + "," + dirStr); } // This method escapes commas in the glob pattern of the given paths. private static String[] getPathStrings(String commaSeparatedPaths) { int length = commaSeparatedPaths.length(); int curlyOpen = 0; int pathStart = 0; boolean globPattern = false; List<String> pathStrings = new ArrayList<String>(); for (int i = 0; i < length; i++) { char ch = commaSeparatedPaths.charAt(i); switch (ch) { case '{': { curlyOpen++; if (!globPattern) { globPattern = true; } break; } case '}': { curlyOpen--; if (curlyOpen == 0 && globPattern) { globPattern = false; } break; } case ',': { if (!globPattern) { pathStrings.add(commaSeparatedPaths.substring(pathStart, i)); pathStart = i + 1; } break; } default: continue; // nothing special to do for this character } } pathStrings.add(commaSeparatedPaths.substring(pathStart, length)); return pathStrings.toArray(new String[0]); } /** * Get the list of input {@link Path}s for the map-reduce job. * * @param context The job * @return the list of input {@link Path}s for the map-reduce job. */ public static Path[] getInputPaths(JobContext context) { String dirs = context.getConfiguration().get(INPUT_DIR, ""); String[] list = StringUtils.split(dirs); Path[] result = new Path[list.length]; for (int i = 0; i < list.length; i++) { result[i] = new Path(StringUtils.unEscapeString(list[i])); } return result; } }
/** This class returns build information about Hadoop components. */ @InterfaceAudience.Private @InterfaceStability.Unstable public class VersionInfo { private static final /* LogLOG=LogFactory.getLog(VersionInfo.class); */ UtilNamespace LOG = LoggerFactory.getLogger(UtilNamespace.class, new SimpleLogger()); private Properties info; protected VersionInfo(String component) { info = new Properties(); String versionInfoFile = component + "-version-info.properties"; InputStream is = null; try { is = Thread.currentThread().getContextClassLoader().getResourceAsStream(versionInfoFile); if (is == null) { throw new IOException("Resource not found"); } info.load(is); } catch (IOException ex) { LogFactory.getLog(getClass()) .warn("Could not read '" + versionInfoFile + "', " + ex.toString(), ex); } finally { IOUtils.closeStream(is); } } protected String _getVersion() { return info.getProperty("version", "Unknown"); } protected String _getRevision() { return info.getProperty("revision", "Unknown"); } protected String _getBranch() { return info.getProperty("branch", "Unknown"); } protected String _getDate() { return info.getProperty("date", "Unknown"); } protected String _getUser() { return info.getProperty("user", "Unknown"); } protected String _getUrl() { return info.getProperty("url", "Unknown"); } protected String _getSrcChecksum() { return info.getProperty("srcChecksum", "Unknown"); } protected String _getBuildVersion() { return getVersion() + " from " + _getRevision() + " by " + _getUser() + " source checksum " + _getSrcChecksum(); } protected String _getProtocVersion() { return info.getProperty("protocVersion", "Unknown"); } private static VersionInfo COMMON_VERSION_INFO = new VersionInfo("common"); /** * Get the Hadoop version. * * @return the Hadoop version string, eg. "0.6.3-dev" */ public static String getVersion() { return COMMON_VERSION_INFO._getVersion(); } /** * Get the subversion revision number for the root directory * * @return the revision number, eg. "451451" */ public static String getRevision() { return COMMON_VERSION_INFO._getRevision(); } /** * Get the branch on which this originated. * * @return The branch name, e.g. "trunk" or "branches/branch-0.20" */ public static String getBranch() { return COMMON_VERSION_INFO._getBranch(); } /** * The date that Hadoop was compiled. * * @return the compilation date in unix date format */ public static String getDate() { return COMMON_VERSION_INFO._getDate(); } /** * The user that compiled Hadoop. * * @return the username of the user */ public static String getUser() { return COMMON_VERSION_INFO._getUser(); } /** Get the subversion URL for the root Hadoop directory. */ public static String getUrl() { return COMMON_VERSION_INFO._getUrl(); } /** Get the checksum of the source files from which Hadoop was built. */ public static String getSrcChecksum() { return COMMON_VERSION_INFO._getSrcChecksum(); } /** Returns the buildVersion which includes version, revision, user and date. */ public static String getBuildVersion() { return COMMON_VERSION_INFO._getBuildVersion(); } /** Returns the protoc version used for the build. */ public static String getProtocVersion() { return COMMON_VERSION_INFO._getProtocVersion(); } public static void main(String[] args) { /* LOG.debug("version: "+getVersion()) */ LOG.version(getVersion()).tag("methodCall").debug(); System.out.println("Hadoop " + getVersion()); System.out.println("Subversion " + getUrl() + " -r " + getRevision()); System.out.println("Compiled by " + getUser() + " on " + getDate()); System.out.println("Compiled with protoc " + getProtocVersion()); System.out.println("From source with checksum " + getSrcChecksum()); System.out.println( "This command was run using " + ClassUtil.findContainingJar(VersionInfo.class)); } }
/** A class for file/directory permissions. */ @InterfaceAudience.Public @InterfaceStability.Stable public class FsPermission implements Writable { private static final /* LogLOG=LogFactory.getLog(FsPermission.class); */ FsNamespace LOG = LoggerFactory.getLogger(FsNamespace.class, new SimpleLogger()); static final WritableFactory FACTORY = new WritableFactory() { @Override public Writable newInstance() { return new FsPermission(); } }; static { // register a ctor WritableFactories.setFactory(FsPermission.class, FACTORY); WritableFactories.setFactory(ImmutableFsPermission.class, FACTORY); } /** Maximum acceptable length of a permission string to parse */ public static final int MAX_PERMISSION_LENGTH = 10; /** Create an immutable {@link FsPermission} object. */ public static FsPermission createImmutable(short permission) { return new ImmutableFsPermission(permission); } // POSIX permission style private FsAction useraction = null; private FsAction groupaction = null; private FsAction otheraction = null; private boolean stickyBit = false; private FsPermission() {} /** * Construct by the given {@link FsAction}. * * @param u user action * @param g group action * @param o other action */ public FsPermission(FsAction u, FsAction g, FsAction o) { this(u, g, o, false); } public FsPermission(FsAction u, FsAction g, FsAction o, boolean sb) { set(u, g, o, sb); } /** * Construct by the given mode. * * @param mode * @see #toShort() */ public FsPermission(short mode) { fromShort(mode); } /** * Copy constructor * * @param other other permission */ public FsPermission(FsPermission other) { this.useraction = other.useraction; this.groupaction = other.groupaction; this.otheraction = other.otheraction; this.stickyBit = other.stickyBit; } /** * Construct by given mode, either in octal or symbolic format. * * @param mode mode as a string, either in octal or symbolic format * @throws IllegalArgumentException if <code>mode</code> is invalid */ public FsPermission(String mode) { this(new UmaskParser(mode).getUMask()); } /** Return user {@link FsAction}. */ public FsAction getUserAction() { return useraction; } /** Return group {@link FsAction}. */ public FsAction getGroupAction() { return groupaction; } /** Return other {@link FsAction}. */ public FsAction getOtherAction() { return otheraction; } private void set(FsAction u, FsAction g, FsAction o, boolean sb) { useraction = u; groupaction = g; otheraction = o; stickyBit = sb; } public void fromShort(short n) { FsAction[] v = FSACTION_VALUES; set(v[(n >>> 6) & 7], v[(n >>> 3) & 7], v[n & 7], (((n >>> 9) & 1) == 1)); } @Override public void write(DataOutput out) throws IOException { out.writeShort(toShort()); } @Override public void readFields(DataInput in) throws IOException { fromShort(in.readShort()); } /** Create and initialize a {@link FsPermission} from {@link DataInput}. */ public static FsPermission read(DataInput in) throws IOException { FsPermission p = new FsPermission(); p.readFields(in); return p; } /** Encode the object to a short. */ public short toShort() { int s = (stickyBit ? 1 << 9 : 0) | (useraction.ordinal() << 6) | (groupaction.ordinal() << 3) | otheraction.ordinal(); return (short) s; } @Override public boolean equals(Object obj) { if (obj instanceof FsPermission) { FsPermission that = (FsPermission) obj; return this.useraction == that.useraction && this.groupaction == that.groupaction && this.otheraction == that.otheraction && this.stickyBit == that.stickyBit; } return false; } @Override public int hashCode() { return toShort(); } @Override public String toString() { String str = useraction.SYMBOL + groupaction.SYMBOL + otheraction.SYMBOL; if (stickyBit) { StringBuilder str2 = new StringBuilder(str); str2.replace( str2.length() - 1, str2.length(), otheraction.implies(FsAction.EXECUTE) ? "t" : "T"); str = str2.toString(); } return str; } /** * Apply a umask to this permission and return a new one. * * <p>The umask is used by create, mkdir, and other Hadoop filesystem operations. The mode * argument for these operations is modified by removing the bits which are set in the umask. * Thus, the umask limits the permissions which newly created files and directories get. * * @param umask The umask to use * @return The effective permission */ public FsPermission applyUMask(FsPermission umask) { return new FsPermission( useraction.and(umask.useraction.not()), groupaction.and(umask.groupaction.not()), otheraction.and(umask.otheraction.not())); } /** * umask property label deprecated key and code in getUMask method to accommodate it may be * removed in version .23 */ public static final String DEPRECATED_UMASK_LABEL = "dfs.umask"; public static final String UMASK_LABEL = CommonConfigurationKeys.FS_PERMISSIONS_UMASK_KEY; public static final int DEFAULT_UMASK = CommonConfigurationKeys.FS_PERMISSIONS_UMASK_DEFAULT; private static final FsAction[] FSACTION_VALUES = FsAction.values(); /** * Get the user file creation mask (umask) * * <p>{@code UMASK_LABEL} config param has umask value that is either symbolic or octal. * * <p>Symbolic umask is applied relative to file mode creation mask; the permission op characters * '+' clears the corresponding bit in the mask, '-' sets bits in the mask. * * <p>Octal umask, the specified bits are set in the file mode creation mask. * * <p>{@code DEPRECATED_UMASK_LABEL} config param has umask value set to decimal. */ public static FsPermission getUMask(Configuration conf) { int umask = DEFAULT_UMASK; // To ensure backward compatibility first use the deprecated key. // If the deprecated key is not present then check for the new key if (conf != null) { String confUmask = conf.get(UMASK_LABEL); int oldUmask = conf.getInt(DEPRECATED_UMASK_LABEL, Integer.MIN_VALUE); try { if (confUmask != null) { umask = new UmaskParser(confUmask).getUMask(); } } catch (IllegalArgumentException iae) { // Provide more explanation for user-facing message String type = iae instanceof NumberFormatException ? "decimal" : "octal or symbolic"; String error = "Unable to parse configuration " + UMASK_LABEL + " with value " + confUmask + " as " + type + " umask."; /* LOG.warn(error) */ LOG.error(error).warn(); // If oldUmask is not set, then throw the exception if (oldUmask == Integer.MIN_VALUE) { throw new IllegalArgumentException(error); } } if (oldUmask != Integer.MIN_VALUE) { // Property was set with old key if (umask != oldUmask) { /* LOG.warn(DEPRECATED_UMASK_LABEL+" configuration key is deprecated. "+"Convert to "+UMASK_LABEL+", using octal or symbolic umask "+"specifications.") */ LOG.configuration_key_deprecated_convert_usi(DEPRECATED_UMASK_LABEL, UMASK_LABEL).warn(); // Old and new umask values do not match - Use old umask umask = oldUmask; } } } return new FsPermission((short) umask); } public boolean getStickyBit() { return stickyBit; } /** Set the user file creation mask (umask) */ public static void setUMask(Configuration conf, FsPermission umask) { conf.set(UMASK_LABEL, String.format("%1$03o", umask.toShort())); conf.setInt(DEPRECATED_UMASK_LABEL, umask.toShort()); } /** * Get the default permission for directory and symlink. In previous versions, this default * permission was also used to create files, so files created end up with ugo+x permission. See * HADOOP-9155 for detail. Two new methods are added to solve this, please use {@link * FsPermission#getDirDefault()} for directory, and use {@link FsPermission#getFileDefault()} for * file. This method is kept for compatibility. */ public static FsPermission getDefault() { return new FsPermission((short) 00777); } /** Get the default permission for directory. */ public static FsPermission getDirDefault() { return new FsPermission((short) 00777); } /** Get the default permission for file. */ public static FsPermission getFileDefault() { return new FsPermission((short) 00666); } /** Get the default permission for cache pools. */ public static FsPermission getCachePoolDefault() { return new FsPermission((short) 00755); } /** * Create a FsPermission from a Unix symbolic permission string * * @param unixSymbolicPermission e.g. "-rw-rw-rw-" */ public static FsPermission valueOf(String unixSymbolicPermission) { if (unixSymbolicPermission == null) { return null; } else if (unixSymbolicPermission.length() != MAX_PERMISSION_LENGTH) { throw new IllegalArgumentException( String.format( "length != %d(unixSymbolicPermission=%s)", MAX_PERMISSION_LENGTH, unixSymbolicPermission)); } int n = 0; for (int i = 1; i < unixSymbolicPermission.length(); i++) { n = n << 1; char c = unixSymbolicPermission.charAt(i); n += (c == '-' || c == 'T' || c == 'S') ? 0 : 1; } // Add sticky bit value if set if (unixSymbolicPermission.charAt(9) == 't' || unixSymbolicPermission.charAt(9) == 'T') n += 01000; return new FsPermission((short) n); } private static class ImmutableFsPermission extends FsPermission { public ImmutableFsPermission(short permission) { super(permission); } @Override public FsPermission applyUMask(FsPermission umask) { throw new UnsupportedOperationException(); } @Override public void readFields(DataInput in) throws IOException { throw new UnsupportedOperationException(); } } }
/** This class provides a way to interact with history files in a thread safe manor. */ @InterfaceAudience.Public @InterfaceStability.Unstable public class HistoryFileManager extends AbstractService { // private static final /* LogLOG=LogFactory.getLog(HistoryFileManager.class); */ // MapreduceNamespace LOG = LoggerFactory.getLogger(MapreduceNamespace.class, new // SimpleLogger()); private static final /* LogSUMMARY_LOG=LogFactory.getLog(JobSummary.class); */ MapreduceNamespace LOG = LoggerFactory.getLogger(MapreduceNamespace.class, new SimpleLogger()); private static enum HistoryInfoState { IN_INTERMEDIATE, IN_DONE, DELETED, MOVE_FAILED }; private static String DONE_BEFORE_SERIAL_TAIL = JobHistoryUtils.doneSubdirsBeforeSerialTail(); /** * Maps between a serial number (generated based on jobId) and the timestamp component(s) to which * it belongs. Facilitates jobId based searches. If a jobId is not found in this list - it will * not be found. */ private static class SerialNumberIndex { private SortedMap<String, Set<String>> cache; private int maxSize; public SerialNumberIndex(int maxSize) { this.cache = new TreeMap<String, Set<String>>(); this.maxSize = maxSize; } public synchronized void add(String serialPart, String timestampPart) { if (!cache.containsKey(serialPart)) { cache.put(serialPart, new HashSet<String>()); if (cache.size() > maxSize) { String key = cache.firstKey(); /* LOG.error("Dropping "+key+" from the SerialNumberIndex. We will no "+"longer be able to see jobs that are in that serial index for "+cache.get(key)) */ LOG.dropping_from_serialnumberindex_will_lon(key, String.valueOf(cache.get(key))) .tag("methodCall") .error(); cache.remove(key); } } Set<String> datePartSet = cache.get(serialPart); datePartSet.add(timestampPart); } public synchronized void remove(String serialPart, String timeStampPart) { if (cache.containsKey(serialPart)) { Set<String> set = cache.get(serialPart); set.remove(timeStampPart); if (set.isEmpty()) { cache.remove(serialPart); } } } public synchronized Set<String> get(String serialPart) { Set<String> found = cache.get(serialPart); if (found != null) { return new HashSet<String>(found); } return null; } } /** * Wrapper around {@link ConcurrentSkipListMap} that maintains size along side for O(1) size() * implementation for use in JobListCache. * * <p>Note: The size is not updated atomically with changes additions/removals. This race can lead * to size() returning an incorrect size at times. */ static class JobIdHistoryFileInfoMap { private ConcurrentSkipListMap<JobId, HistoryFileInfo> cache; private AtomicInteger mapSize; JobIdHistoryFileInfoMap() { cache = new ConcurrentSkipListMap<JobId, HistoryFileInfo>(); mapSize = new AtomicInteger(); } public HistoryFileInfo putIfAbsent(JobId key, HistoryFileInfo value) { HistoryFileInfo ret = cache.putIfAbsent(key, value); if (ret == null) { mapSize.incrementAndGet(); } return ret; } public HistoryFileInfo remove(JobId key) { HistoryFileInfo ret = cache.remove(key); if (ret != null) { mapSize.decrementAndGet(); } return ret; } /** * Returns the recorded size of the internal map. Note that this could be out of sync with the * actual size of the map * * @return "recorded" size */ public int size() { return mapSize.get(); } public HistoryFileInfo get(JobId key) { return cache.get(key); } public NavigableSet<JobId> navigableKeySet() { return cache.navigableKeySet(); } public Collection<HistoryFileInfo> values() { return cache.values(); } } static class JobListCache { private JobIdHistoryFileInfoMap cache; private int maxSize; private long maxAge; public JobListCache(int maxSize, long maxAge) { this.maxSize = maxSize; this.maxAge = maxAge; this.cache = new JobIdHistoryFileInfoMap(); } public HistoryFileInfo addIfAbsent(HistoryFileInfo fileInfo) { JobId jobId = fileInfo.getJobId(); if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Adding "+jobId+" to job list cache with "+fileInfo.getJobIndexInfo()) */ LOG.adding_job_list_cache_with(jobId.toString(), String.valueOf(fileInfo.getJobIndexInfo())) .tag("methodCall") .debug(); } HistoryFileInfo old = cache.putIfAbsent(jobId, fileInfo); if (cache.size() > maxSize) { // There is a race here, where more then one thread could be trying to // remove entries. This could result in too many entries being removed // from the cache. This is considered OK as the size of the cache // should be rather large, and we would rather have performance over // keeping the cache size exactly at the maximum. Iterator<JobId> keys = cache.navigableKeySet().iterator(); long cutoff = System.currentTimeMillis() - maxAge; while (cache.size() > maxSize && keys.hasNext()) { JobId key = keys.next(); HistoryFileInfo firstValue = cache.get(key); if (firstValue != null) { synchronized (firstValue) { if (firstValue.isMovePending()) { if (firstValue.didMoveFail() && firstValue.jobIndexInfo.getFinishTime() <= cutoff) { cache.remove(key); // Now lets try to delete it try { firstValue.delete(); } catch (IOException e) { /* LOG.error("Error while trying to delete history files"+" that could not be moved to done.",e) */ LOG.error_while_trying_delete_history_files_(e.toString()).error(); } } else { /* LOG.warn("Waiting to remove "+key+" from JobListCache because it is not in done yet.") */ LOG.waiting_remove_from_joblistcache_because(key.toString()).warn(); } } else { cache.remove(key); } } } } } return old; } public void delete(HistoryFileInfo fileInfo) { if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Removing from cache "+fileInfo) */ LOG.removing_from_cache(fileInfo.toString()).debug(); } cache.remove(fileInfo.getJobId()); } public Collection<HistoryFileInfo> values() { return new ArrayList<HistoryFileInfo>(cache.values()); } public HistoryFileInfo get(JobId jobId) { return cache.get(jobId); } } /** * This class represents a user dir in the intermediate done directory. This is mostly for locking * purposes. */ private class UserLogDir { long modTime = 0; public synchronized void scanIfNeeded(FileStatus fs) { long newModTime = fs.getModificationTime(); if (modTime != newModTime) { Path p = fs.getPath(); try { scanIntermediateDirectory(p); // If scanning fails, we will scan again. We assume the failure is // temporary. modTime = newModTime; } catch (IOException e) { /* LOG.error("Error while trying to scan the directory "+p,e) */ LOG.error_while_trying_scan_directory(p.toString(), e.toString()).error(); } } else { if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Scan not needed of "+fs.getPath()) */ LOG.scan_not_needed(String.valueOf(fs.getPath())).tag("methodCall").debug(); } } } } public class HistoryFileInfo { private Path historyFile; private Path confFile; private Path summaryFile; private JobIndexInfo jobIndexInfo; private HistoryInfoState state; private HistoryFileInfo( Path historyFile, Path confFile, Path summaryFile, JobIndexInfo jobIndexInfo, boolean isInDone) { this.historyFile = historyFile; this.confFile = confFile; this.summaryFile = summaryFile; this.jobIndexInfo = jobIndexInfo; state = isInDone ? HistoryInfoState.IN_DONE : HistoryInfoState.IN_INTERMEDIATE; } @VisibleForTesting synchronized boolean isMovePending() { return state == HistoryInfoState.IN_INTERMEDIATE || state == HistoryInfoState.MOVE_FAILED; } @VisibleForTesting synchronized boolean didMoveFail() { return state == HistoryInfoState.MOVE_FAILED; } /** @return true if the files backed by this were deleted. */ public synchronized boolean isDeleted() { return state == HistoryInfoState.DELETED; } @Override public String toString() { return "HistoryFileInfo jobID " + getJobId() + " historyFile = " + historyFile; } private synchronized void moveToDone() throws IOException { if (LogGlobal.isDebugEnabled()) { /* LOG.debug("moveToDone: "+historyFile) */ LOG.movetodone(historyFile.toString()).debug(); } if (!isMovePending()) { // It was either deleted or is already in done. Either way do nothing if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Move no longer pending") */ LOG.move_longer_pending().debug(); } return; } try { long completeTime = jobIndexInfo.getFinishTime(); if (completeTime == 0) { completeTime = System.currentTimeMillis(); } JobId jobId = jobIndexInfo.getJobId(); List<Path> paths = new ArrayList<Path>(2); if (historyFile == null) { /* LOG.info("No file for job-history with "+jobId+" found in cache!") */ LOG.file_for_job_history_with_found(jobId.toString()).info(); } else { paths.add(historyFile); } if (confFile == null) { /* LOG.info("No file for jobConf with "+jobId+" found in cache!") */ LOG.file_for_jobconf_with_found_cache(jobId.toString()).info(); } else { paths.add(confFile); } if (summaryFile == null) { /* LOG.info("No summary file for job: "+jobId) */ LOG.summary_file_for_job(jobId.toString()).info(); } else { String jobSummaryString = getJobSummary(intermediateDoneDirFc, summaryFile); LOG.summary_file_for_job(jobSummaryString); /* LOG.info("Deleting JobSummary file: ["+summaryFile+"]") */ LOG.deleting_jobsummary_file(summaryFile.toString()).info(); intermediateDoneDirFc.delete(summaryFile, false); summaryFile = null; } Path targetDir = canonicalHistoryLogPath(jobId, completeTime); addDirectoryToSerialNumberIndex(targetDir); makeDoneSubdir(targetDir); if (historyFile != null) { Path toPath = doneDirFc.makeQualified(new Path(targetDir, historyFile.getName())); if (!toPath.equals(historyFile)) { moveToDoneNow(historyFile, toPath); historyFile = toPath; } } if (confFile != null) { Path toPath = doneDirFc.makeQualified(new Path(targetDir, confFile.getName())); if (!toPath.equals(confFile)) { moveToDoneNow(confFile, toPath); confFile = toPath; } } state = HistoryInfoState.IN_DONE; } catch (Throwable t) { /* LOG.error("Error while trying to move a job to done",t) */ LOG.error_while_trying_move_job_done(t.toString()).error(); this.state = HistoryInfoState.MOVE_FAILED; } } /** * Parse a job from the JobHistoryFile, if the underlying file is not going to be deleted. * * @return the Job or null if the underlying file was deleted. * @throws IOException if there is an error trying to read the file. */ public synchronized Job loadJob() throws IOException { return new CompletedJob( conf, jobIndexInfo.getJobId(), historyFile, false, jobIndexInfo.getUser(), this, aclsMgr); } /** * Return the history file. This should only be used for testing. * * @return the history file. */ synchronized Path getHistoryFile() { return historyFile; } protected synchronized void delete() throws IOException { if (LogGlobal.isDebugEnabled()) { /* LOG.debug("deleting "+historyFile+" and "+confFile) */ LOG.deleting_and(historyFile.toString(), confFile.toString()).debug(); } state = HistoryInfoState.DELETED; doneDirFc.delete(doneDirFc.makeQualified(historyFile), false); doneDirFc.delete(doneDirFc.makeQualified(confFile), false); } public JobIndexInfo getJobIndexInfo() { return jobIndexInfo; } public JobId getJobId() { return jobIndexInfo.getJobId(); } public synchronized Path getConfFile() { return confFile; } public synchronized Configuration loadConfFile() throws IOException { FileContext fc = FileContext.getFileContext(confFile.toUri(), conf); Configuration jobConf = new Configuration(false); jobConf.addResource(fc.open(confFile), confFile.toString()); return jobConf; } } private SerialNumberIndex serialNumberIndex = null; protected JobListCache jobListCache = null; // Maintains a list of known done subdirectories. private final Set<Path> existingDoneSubdirs = Collections.synchronizedSet(new HashSet<Path>()); /** * Maintains a mapping between intermediate user directories and the last known modification time. */ private ConcurrentMap<String, UserLogDir> userDirModificationTimeMap = new ConcurrentHashMap<String, UserLogDir>(); private JobACLsManager aclsMgr; @VisibleForTesting Configuration conf; private String serialNumberFormat; private Path doneDirPrefixPath = null; // folder for completed jobs private FileContext doneDirFc; // done Dir FileContext private Path intermediateDoneDirPath = null; // Intermediate Done Dir Path private FileContext intermediateDoneDirFc; // Intermediate Done Dir // FileContext @VisibleForTesting protected ThreadPoolExecutor moveToDoneExecutor = null; private long maxHistoryAge = 0; public HistoryFileManager() { super(HistoryFileManager.class.getName()); } @Override protected void serviceInit(Configuration conf) throws Exception { this.conf = conf; int serialNumberLowDigits = 3; serialNumberFormat = ("%0" + (JobHistoryUtils.SERIAL_NUMBER_DIRECTORY_DIGITS + serialNumberLowDigits) + "d"); long maxFSWaitTime = conf.getLong( JHAdminConfig.MR_HISTORY_MAX_START_WAIT_TIME, JHAdminConfig.DEFAULT_MR_HISTORY_MAX_START_WAIT_TIME); createHistoryDirs(new SystemClock(), 10 * 1000, maxFSWaitTime); this.aclsMgr = new JobACLsManager(conf); maxHistoryAge = conf.getLong(JHAdminConfig.MR_HISTORY_MAX_AGE_MS, JHAdminConfig.DEFAULT_MR_HISTORY_MAX_AGE); jobListCache = createJobListCache(); serialNumberIndex = new SerialNumberIndex( conf.getInt( JHAdminConfig.MR_HISTORY_DATESTRING_CACHE_SIZE, JHAdminConfig.DEFAULT_MR_HISTORY_DATESTRING_CACHE_SIZE)); int numMoveThreads = conf.getInt( JHAdminConfig.MR_HISTORY_MOVE_THREAD_COUNT, JHAdminConfig.DEFAULT_MR_HISTORY_MOVE_THREAD_COUNT); ThreadFactory tf = new ThreadFactoryBuilder().setNameFormat("MoveIntermediateToDone Thread #%d").build(); moveToDoneExecutor = new ThreadPoolExecutor( numMoveThreads, numMoveThreads, 1, TimeUnit.HOURS, new LinkedBlockingQueue<Runnable>(), tf); super.serviceInit(conf); } @VisibleForTesting void createHistoryDirs(Clock clock, long intervalCheckMillis, long timeOutMillis) throws IOException { long start = clock.getTime(); boolean done = false; int counter = 0; while (!done && ((timeOutMillis == -1) || (clock.getTime() - start < timeOutMillis))) { done = tryCreatingHistoryDirs(counter++ % 3 == 0); // log every 3 attempts, 30sec try { Thread.sleep(intervalCheckMillis); } catch (InterruptedException ex) { throw new YarnRuntimeException(ex); } } if (!done) { throw new YarnRuntimeException( "Timed out '" + timeOutMillis + "ms' waiting for FileSystem to become available"); } } /** * DistributedFileSystem returns a RemoteException with a message stating SafeModeException in it. * So this is only way to check it is because of being in safe mode. */ private boolean isBecauseSafeMode(Throwable ex) { return ex.toString().contains("SafeModeException"); } /** * Returns TRUE if the history dirs were created, FALSE if they could not be created because the * FileSystem is not reachable or in safe mode and throws and exception otherwise. */ @VisibleForTesting boolean tryCreatingHistoryDirs(boolean logWait) throws IOException { boolean succeeded = true; String doneDirPrefix = JobHistoryUtils.getConfiguredHistoryServerDoneDirPrefix(conf); try { doneDirPrefixPath = FileContext.getFileContext(conf).makeQualified(new Path(doneDirPrefix)); doneDirFc = FileContext.getFileContext(doneDirPrefixPath.toUri(), conf); doneDirFc.setUMask(JobHistoryUtils.HISTORY_DONE_DIR_UMASK); mkdir( doneDirFc, doneDirPrefixPath, new FsPermission(JobHistoryUtils.HISTORY_DONE_DIR_PERMISSION)); } catch (ConnectException ex) { if (logWait) { /* LOG.info("Waiting for FileSystem at "+doneDirPrefixPath.toUri().getAuthority()+"to be available") */ LOG.waiting_for_filesystem_available( String.valueOf(doneDirPrefixPath.toUri().getAuthority())) .tag("methodCall") .info(); } succeeded = false; } catch (IOException e) { if (isBecauseSafeMode(e)) { succeeded = false; if (logWait) { /* LOG.info("Waiting for FileSystem at "+doneDirPrefixPath.toUri().getAuthority()+"to be out of safe mode") */ LOG.waiting_for_filesystem_out_safe_mode( String.valueOf(doneDirPrefixPath.toUri().getAuthority())) .tag("methodCall") .info(); } } else { throw new YarnRuntimeException( "Error creating done directory: [" + doneDirPrefixPath + "]", e); } } if (succeeded) { String intermediateDoneDirPrefix = JobHistoryUtils.getConfiguredHistoryIntermediateDoneDirPrefix(conf); try { intermediateDoneDirPath = FileContext.getFileContext(conf).makeQualified(new Path(intermediateDoneDirPrefix)); intermediateDoneDirFc = FileContext.getFileContext(intermediateDoneDirPath.toUri(), conf); mkdir( intermediateDoneDirFc, intermediateDoneDirPath, new FsPermission(JobHistoryUtils.HISTORY_INTERMEDIATE_DONE_DIR_PERMISSIONS.toShort())); } catch (ConnectException ex) { succeeded = false; if (logWait) { /* LOG.info("Waiting for FileSystem at "+intermediateDoneDirPath.toUri().getAuthority()+"to be available") */ LOG.waiting_for_filesystem_available( String.valueOf(intermediateDoneDirPath.toUri().getAuthority())) .tag("methodCall") .info(); } } catch (IOException e) { if (isBecauseSafeMode(e)) { succeeded = false; if (logWait) { /* LOG.info("Waiting for FileSystem at "+intermediateDoneDirPath.toUri().getAuthority()+"to be out of safe mode") */ LOG.waiting_for_filesystem_out_safe_mode( String.valueOf(intermediateDoneDirPath.toUri().getAuthority())) .tag("methodCall") .info(); } } else { throw new YarnRuntimeException( "Error creating intermediate done directory: [" + intermediateDoneDirPath + "]", e); } } } return succeeded; } @Override public void serviceStop() throws Exception { ShutdownThreadsHelper.shutdownExecutorService(moveToDoneExecutor); super.serviceStop(); } protected JobListCache createJobListCache() { return new JobListCache( conf.getInt( JHAdminConfig.MR_HISTORY_JOBLIST_CACHE_SIZE, JHAdminConfig.DEFAULT_MR_HISTORY_JOBLIST_CACHE_SIZE), maxHistoryAge); } private void mkdir(FileContext fc, Path path, FsPermission fsp) throws IOException { if (!fc.util().exists(path)) { try { fc.mkdir(path, fsp, true); FileStatus fsStatus = fc.getFileStatus(path); /* LOG.info("Perms after creating "+fsStatus.getPermission().toShort()+", Expected: "+fsp.toShort()) */ LOG.perms_after_creating_expected( String.valueOf(fsStatus.getPermission().toShort()), String.valueOf(fsp.toShort())) .tag("methodCall") .info(); if (fsStatus.getPermission().toShort() != fsp.toShort()) { /* LOG.info("Explicitly setting permissions to : "+fsp.toShort()+", "+fsp) */ LOG.explicitly_setting_permissions(String.valueOf(fsp.toShort()), fsp.toString()) .tag("methodCall") .info(); fc.setPermission(path, fsp); } } catch (FileAlreadyExistsException e) { /* LOG.info("Directory: ["+path+"] already exists.") */ LOG.directory_already_exists(path.toString()).info(); } } } /** Populates index data structures. Should only be called at initialization times. */ @SuppressWarnings("unchecked") void initExisting() throws IOException { /* LOG.info("Initializing Existing Jobs...") */ LOG.initializing_existing_jobs().info(); List<FileStatus> timestampedDirList = findTimestampedDirectories(); // Sort first just so insertion is in a consistent order Collections.sort(timestampedDirList); for (FileStatus fs : timestampedDirList) { // TODO Could verify the correct format for these directories. addDirectoryToSerialNumberIndex(fs.getPath()); addDirectoryToJobListCache(fs.getPath()); } } private void removeDirectoryFromSerialNumberIndex(Path serialDirPath) { String serialPart = serialDirPath.getName(); String timeStampPart = JobHistoryUtils.getTimestampPartFromPath(serialDirPath.toString()); if (timeStampPart == null) { /* LOG.warn("Could not find timestamp portion from path: "+serialDirPath.toString()+". Continuing with next") */ LOG.could_not_find_timestamp_portion_from_co(String.valueOf(serialDirPath.toString())) .tag("methodCall") .warn(); return; } if (serialPart == null) { /* LOG.warn("Could not find serial portion from path: "+serialDirPath.toString()+". Continuing with next") */ LOG.could_not_find_serial_portion_from_conti(String.valueOf(serialDirPath.toString())) .tag("methodCall") .warn(); return; } serialNumberIndex.remove(serialPart, timeStampPart); } private void addDirectoryToSerialNumberIndex(Path serialDirPath) { if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Adding "+serialDirPath+" to serial index") */ LOG.adding_serial_index(serialDirPath.toString()).debug(); } String serialPart = serialDirPath.getName(); String timestampPart = JobHistoryUtils.getTimestampPartFromPath(serialDirPath.toString()); if (timestampPart == null) { /* LOG.warn("Could not find timestamp portion from path: "+serialDirPath+". Continuing with next") */ LOG.could_not_find_timestamp_portion_from_co(serialDirPath.toString()).warn(); return; } if (serialPart == null) { /* LOG.warn("Could not find serial portion from path: "+serialDirPath.toString()+". Continuing with next") */ LOG.could_not_find_serial_portion_from_conti(String.valueOf(serialDirPath.toString())) .tag("methodCall") .warn(); } else { serialNumberIndex.add(serialPart, timestampPart); } } private void addDirectoryToJobListCache(Path path) throws IOException { if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Adding "+path+" to job list cache.") */ LOG.adding_job_list_cache(path.toString()).debug(); } List<FileStatus> historyFileList = scanDirectoryForHistoryFiles(path, doneDirFc); for (FileStatus fs : historyFileList) { if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Adding in history for "+fs.getPath()) */ LOG.adding_history_for(String.valueOf(fs.getPath())).tag("methodCall").debug(); } JobIndexInfo jobIndexInfo = FileNameIndexUtils.getIndexInfo(fs.getPath().getName()); String confFileName = JobHistoryUtils.getIntermediateConfFileName(jobIndexInfo.getJobId()); String summaryFileName = JobHistoryUtils.getIntermediateSummaryFileName(jobIndexInfo.getJobId()); HistoryFileInfo fileInfo = new HistoryFileInfo( fs.getPath(), new Path(fs.getPath().getParent(), confFileName), new Path(fs.getPath().getParent(), summaryFileName), jobIndexInfo, true); jobListCache.addIfAbsent(fileInfo); } } private static List<FileStatus> scanDirectory(Path path, FileContext fc, PathFilter pathFilter) throws IOException { path = fc.makeQualified(path); List<FileStatus> jhStatusList = new ArrayList<FileStatus>(); RemoteIterator<FileStatus> fileStatusIter = fc.listStatus(path); while (fileStatusIter.hasNext()) { FileStatus fileStatus = fileStatusIter.next(); Path filePath = fileStatus.getPath(); if (fileStatus.isFile() && pathFilter.accept(filePath)) { jhStatusList.add(fileStatus); } } return jhStatusList; } protected List<FileStatus> scanDirectoryForHistoryFiles(Path path, FileContext fc) throws IOException { return scanDirectory(path, fc, JobHistoryUtils.getHistoryFileFilter()); } /** * Finds all history directories with a timestamp component by scanning the filesystem. Used when * the JobHistory server is started. * * @return list of history directories */ protected List<FileStatus> findTimestampedDirectories() throws IOException { List<FileStatus> fsList = JobHistoryUtils.localGlobber(doneDirFc, doneDirPrefixPath, DONE_BEFORE_SERIAL_TAIL); return fsList; } /** * Scans the intermediate directory to find user directories. Scans these for history files if the * modification time for the directory has changed. Once it finds history files it starts the * process of moving them to the done directory. * * @throws IOException if there was a error while scanning */ void scanIntermediateDirectory() throws IOException { // TODO it would be great to limit how often this happens, except in the // case where we are looking for a particular job. List<FileStatus> userDirList = JobHistoryUtils.localGlobber(intermediateDoneDirFc, intermediateDoneDirPath, ""); /* LOG.debug("Scanning intermediate dirs") */ LOG.scanning_intermediate_dirs().debug(); for (FileStatus userDir : userDirList) { String name = userDir.getPath().getName(); UserLogDir dir = userDirModificationTimeMap.get(name); if (dir == null) { dir = new UserLogDir(); UserLogDir old = userDirModificationTimeMap.putIfAbsent(name, dir); if (old != null) { dir = old; } } dir.scanIfNeeded(userDir); } } /** * Scans the specified path and populates the intermediate cache. * * @param absPath * @throws IOException */ private void scanIntermediateDirectory(final Path absPath) throws IOException { if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Scanning intermediate dir "+absPath) */ LOG.scanning_intermediate_dir(absPath.toString()).debug(); } List<FileStatus> fileStatusList = scanDirectoryForHistoryFiles(absPath, intermediateDoneDirFc); if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Found "+fileStatusList.size()+" files") */ LOG.found_files(String.valueOf(fileStatusList.size())).tag("methodCall").debug(); } for (FileStatus fs : fileStatusList) { if (LogGlobal.isDebugEnabled()) { /* LOG.debug("scanning file: "+fs.getPath()) */ LOG.scanning_file(String.valueOf(fs.getPath())).tag("methodCall").debug(); } JobIndexInfo jobIndexInfo = FileNameIndexUtils.getIndexInfo(fs.getPath().getName()); String confFileName = JobHistoryUtils.getIntermediateConfFileName(jobIndexInfo.getJobId()); String summaryFileName = JobHistoryUtils.getIntermediateSummaryFileName(jobIndexInfo.getJobId()); HistoryFileInfo fileInfo = new HistoryFileInfo( fs.getPath(), new Path(fs.getPath().getParent(), confFileName), new Path(fs.getPath().getParent(), summaryFileName), jobIndexInfo, false); final HistoryFileInfo old = jobListCache.addIfAbsent(fileInfo); if (old == null || old.didMoveFail()) { final HistoryFileInfo found = (old == null) ? fileInfo : old; long cutoff = System.currentTimeMillis() - maxHistoryAge; if (found.getJobIndexInfo().getFinishTime() <= cutoff) { try { found.delete(); } catch (IOException e) { /* LOG.warn("Error cleaning up a HistoryFile that is out of date.",e) */ LOG.error_cleaning_historyfile_that_out_date(e.toString()).warn(); } } else { if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Scheduling move to done of "+found) */ LOG.scheduling_move_done(found.toString()).debug(); } moveToDoneExecutor.execute( new Runnable() { @Override public void run() { try { found.moveToDone(); } catch (IOException e) { /* LOG.info("Failed to process fileInfo for job: "+found.getJobId(),e) */ LOG.failed_process_fileinfo_for_job( String.valueOf(found.getJobId()), e.toString()) .tag("methodCall") .info(); } } }); } } else if (old != null && !old.isMovePending()) { // This is a duplicate so just delete it if (LogGlobal.isDebugEnabled()) { /* LOG.debug("Duplicate: deleting") */ LOG.duplicate_deleting().debug(); } fileInfo.delete(); } } } /** * Searches the job history file FileStatus list for the specified JobId. * * @param fileStatusList fileStatus list of Job History Files. * @param jobId The JobId to find. * @return A FileInfo object for the jobId, null if not found. * @throws IOException */ private HistoryFileInfo getJobFileInfo(List<FileStatus> fileStatusList, JobId jobId) throws IOException { for (FileStatus fs : fileStatusList) { JobIndexInfo jobIndexInfo = FileNameIndexUtils.getIndexInfo(fs.getPath().getName()); if (jobIndexInfo.getJobId().equals(jobId)) { String confFileName = JobHistoryUtils.getIntermediateConfFileName(jobIndexInfo.getJobId()); String summaryFileName = JobHistoryUtils.getIntermediateSummaryFileName(jobIndexInfo.getJobId()); HistoryFileInfo fileInfo = new HistoryFileInfo( fs.getPath(), new Path(fs.getPath().getParent(), confFileName), new Path(fs.getPath().getParent(), summaryFileName), jobIndexInfo, true); return fileInfo; } } return null; } /** * Scans old directories known by the idToDateString map for the specified jobId. If the number of * directories is higher than the supported size of the idToDateString cache, the jobId will not * be found. * * @param jobId the jobId. * @return * @throws IOException */ private HistoryFileInfo scanOldDirsForJob(JobId jobId) throws IOException { String boxedSerialNumber = JobHistoryUtils.serialNumberDirectoryComponent(jobId, serialNumberFormat); Set<String> dateStringSet = serialNumberIndex.get(boxedSerialNumber); if (dateStringSet == null) { return null; } for (String timestampPart : dateStringSet) { Path logDir = canonicalHistoryLogPath(jobId, timestampPart); List<FileStatus> fileStatusList = scanDirectoryForHistoryFiles(logDir, doneDirFc); HistoryFileInfo fileInfo = getJobFileInfo(fileStatusList, jobId); if (fileInfo != null) { return fileInfo; } } return null; } public Collection<HistoryFileInfo> getAllFileInfo() throws IOException { scanIntermediateDirectory(); return jobListCache.values(); } public HistoryFileInfo getFileInfo(JobId jobId) throws IOException { // FileInfo available in cache. HistoryFileInfo fileInfo = jobListCache.get(jobId); if (fileInfo != null) { return fileInfo; } // OK so scan the intermediate to be sure we did not lose it that way scanIntermediateDirectory(); fileInfo = jobListCache.get(jobId); if (fileInfo != null) { return fileInfo; } // Intermediate directory does not contain job. Search through older ones. fileInfo = scanOldDirsForJob(jobId); if (fileInfo != null) { return fileInfo; } return null; } private void moveToDoneNow(final Path src, final Path target) throws IOException { /* LOG.info("Moving "+src.toString()+" to "+target.toString()) */ LOG.moving(String.valueOf(src.toString()), String.valueOf(target.toString())) .tag("methodCall") .info(); intermediateDoneDirFc.rename(src, target, Options.Rename.NONE); } private String getJobSummary(FileContext fc, Path path) throws IOException { Path qPath = fc.makeQualified(path); FSDataInputStream in = fc.open(qPath); String jobSummaryString = in.readUTF(); in.close(); return jobSummaryString; } private void makeDoneSubdir(Path path) throws IOException { try { doneDirFc.getFileStatus(path); existingDoneSubdirs.add(path); } catch (FileNotFoundException fnfE) { try { FsPermission fsp = new FsPermission(JobHistoryUtils.HISTORY_DONE_DIR_PERMISSION); doneDirFc.mkdir(path, fsp, true); FileStatus fsStatus = doneDirFc.getFileStatus(path); /* LOG.info("Perms after creating "+fsStatus.getPermission().toShort()+", Expected: "+fsp.toShort()) */ LOG.perms_after_creating_expected( String.valueOf(fsStatus.getPermission().toShort()), String.valueOf(fsp.toShort())) .tag("methodCall") .info(); if (fsStatus.getPermission().toShort() != fsp.toShort()) { /* LOG.info("Explicitly setting permissions to : "+fsp.toShort()+", "+fsp) */ LOG.explicitly_setting_permissions(String.valueOf(fsp.toShort()), fsp.toString()) .tag("methodCall") .info(); doneDirFc.setPermission(path, fsp); } existingDoneSubdirs.add(path); } catch (FileAlreadyExistsException faeE) { // Nothing to do. } } } private Path canonicalHistoryLogPath(JobId id, String timestampComponent) { return new Path( doneDirPrefixPath, JobHistoryUtils.historyLogSubdirectory(id, timestampComponent, serialNumberFormat)); } private Path canonicalHistoryLogPath(JobId id, long millisecondTime) { String timestampComponent = JobHistoryUtils.timestampDirectoryComponent(millisecondTime); return new Path( doneDirPrefixPath, JobHistoryUtils.historyLogSubdirectory(id, timestampComponent, serialNumberFormat)); } private long getEffectiveTimestamp(long finishTime, FileStatus fileStatus) { if (finishTime == 0) { return fileStatus.getModificationTime(); } return finishTime; } private void deleteJobFromDone(HistoryFileInfo fileInfo) throws IOException { jobListCache.delete(fileInfo); fileInfo.delete(); } List<FileStatus> getHistoryDirsForCleaning(long cutoff) throws IOException { return JobHistoryUtils.getHistoryDirsForCleaning(doneDirFc, doneDirPrefixPath, cutoff); } /** * Clean up older history files. * * @throws IOException on any error trying to remove the entries. */ @SuppressWarnings("unchecked") void clean() throws IOException { long cutoff = System.currentTimeMillis() - maxHistoryAge; boolean halted = false; List<FileStatus> serialDirList = getHistoryDirsForCleaning(cutoff); // Sort in ascending order. Relies on YYYY/MM/DD/Serial Collections.sort(serialDirList); for (FileStatus serialDir : serialDirList) { List<FileStatus> historyFileList = scanDirectoryForHistoryFiles(serialDir.getPath(), doneDirFc); for (FileStatus historyFile : historyFileList) { JobIndexInfo jobIndexInfo = FileNameIndexUtils.getIndexInfo(historyFile.getPath().getName()); long effectiveTimestamp = getEffectiveTimestamp(jobIndexInfo.getFinishTime(), historyFile); if (effectiveTimestamp <= cutoff) { HistoryFileInfo fileInfo = this.jobListCache.get(jobIndexInfo.getJobId()); if (fileInfo == null) { String confFileName = JobHistoryUtils.getIntermediateConfFileName(jobIndexInfo.getJobId()); fileInfo = new HistoryFileInfo( historyFile.getPath(), new Path(historyFile.getPath().getParent(), confFileName), null, jobIndexInfo, true); } deleteJobFromDone(fileInfo); } else { halted = true; break; } } if (!halted) { deleteDir(serialDir); removeDirectoryFromSerialNumberIndex(serialDir.getPath()); existingDoneSubdirs.remove(serialDir.getPath()); } else { break; // Don't scan any more directories. } } } protected boolean deleteDir(FileStatus serialDir) throws AccessControlException, FileNotFoundException, UnsupportedFileSystemException, IOException { return doneDirFc.delete(doneDirFc.makeQualified(serialDir.getPath()), true); } // for test @VisibleForTesting void setMaxHistoryAge(long newValue) { maxHistoryAge = newValue; } }
@InterfaceAudience.Public @InterfaceStability.Evolving public class DefaultCodec implements Configurable, CompressionCodec, DirectDecompressionCodec { private static final /* LogLOG=LogFactory.getLog(DefaultCodec.class); */ IoNamespace LOG = LoggerFactory.getLogger(IoNamespace.class, new SimpleLogger()); Configuration conf; @Override public void setConf(Configuration conf) { this.conf = conf; } @Override public Configuration getConf() { return conf; } @Override public CompressionOutputStream createOutputStream(OutputStream out) throws IOException { // This may leak memory if called in a loop. The createCompressor() call // may cause allocation of an untracked direct-backed buffer if native // libs are being used (even if you close the stream). A Compressor // object should be reused between successive calls. /* LOG.warn("DefaultCodec.createOutputStream() may leak memory. "+"Create a compressor first.") */ LOG.defaultcodec_createoutputstream_may_leak().warn(); return new CompressorStream( out, createCompressor(), conf.getInt("io.file.buffer.size", 4 * 1024)); } @Override public CompressionOutputStream createOutputStream(OutputStream out, Compressor compressor) throws IOException { return new CompressorStream(out, compressor, conf.getInt("io.file.buffer.size", 4 * 1024)); } @Override public Class<? extends Compressor> getCompressorType() { return ZlibFactory.getZlibCompressorType(conf); } @Override public Compressor createCompressor() { return ZlibFactory.getZlibCompressor(conf); } @Override public CompressionInputStream createInputStream(InputStream in) throws IOException { return new DecompressorStream( in, createDecompressor(), conf.getInt("io.file.buffer.size", 4 * 1024)); } @Override public CompressionInputStream createInputStream(InputStream in, Decompressor decompressor) throws IOException { return new DecompressorStream(in, decompressor, conf.getInt("io.file.buffer.size", 4 * 1024)); } @Override public Class<? extends Decompressor> getDecompressorType() { return ZlibFactory.getZlibDecompressorType(conf); } @Override public Decompressor createDecompressor() { return ZlibFactory.getZlibDecompressor(conf); } /** {@inheritDoc} */ @Override public DirectDecompressor createDirectDecompressor() { return ZlibFactory.getZlibDirectDecompressor(conf); } @Override public String getDefaultExtension() { return ".deflate"; } }
/** * This class parses the configured list of fencing methods, and is responsible for trying each one * in turn while logging informative output. * * <p>The fencing methods are configured as a carriage-return separated list. Each line in the list * is of the form: * * <p><code>com.example.foo.MyMethod(arg string)</code> or <code>com.example.foo.MyMethod</code> The * class provided must implement the {@link FenceMethod} interface. The fencing methods that ship * with Hadoop may also be referred to by shortened names: * * <p> * * <ul> * <li><code>shell(/path/to/some/script.sh args...)</code> * <li><code>sshfence(...)</code> (see {@link SshFenceByTcpPort}) * </ul> */ @InterfaceAudience.Private @InterfaceStability.Evolving public class NodeFencer { private static final String CLASS_RE = "([a-zA-Z0-9\\.\\$]+)"; private static final Pattern CLASS_WITH_ARGUMENT = Pattern.compile(CLASS_RE + "\\((.+?)\\)"); private static final Pattern CLASS_WITHOUT_ARGUMENT = Pattern.compile(CLASS_RE); private static final Pattern HASH_COMMENT_RE = Pattern.compile("#.*$"); private static final /* LogLOG=LogFactory.getLog(NodeFencer.class); */ HaNamespace LOG = LoggerFactory.getLogger(HaNamespace.class, new SimpleLogger()); /** Standard fencing methods included with Hadoop. */ private static final Map<String, Class<? extends FenceMethod>> STANDARD_METHODS = ImmutableMap.<String, Class<? extends FenceMethod>>of( "shell", ShellCommandFencer.class, "sshfence", SshFenceByTcpPort.class); private final List<FenceMethodWithArg> methods; NodeFencer(Configuration conf, String spec) throws BadFencingConfigurationException { this.methods = parseMethods(conf, spec); } public static NodeFencer create(Configuration conf, String confKey) throws BadFencingConfigurationException { String confStr = conf.get(confKey); if (confStr == null) { return null; } return new NodeFencer(conf, confStr); } public boolean fence(HAServiceTarget fromSvc) { /* LOG.info("====== Beginning Service Fencing Process... ======") */ LOG.beginning_service_fencing_process().info(); int i = 0; for (FenceMethodWithArg method : methods) { /* LOG.info("Trying method "+(++i)+"/"+methods.size()+": "+method) */ LOG.trying_method((++i), String.valueOf(methods.size()), method.toString()) .tag("mathExp") .tag("methodCall") .info(); try { if (method.method.tryFence(fromSvc, method.arg)) { /* LOG.info("====== Fencing successful by method "+method+" ======") */ LOG.fencing_successful_method(method.toString()).info(); return true; } } catch (BadFencingConfigurationException e) { /* LOG.error("Fencing method "+method+" misconfigured",e) */ LOG.fencing_method_misconfigured(method.toString(), e.toString()).error(); continue; } catch (Throwable t) { /* LOG.error("Fencing method "+method+" failed with an unexpected error.",t) */ LOG.fencing_method_failed_with_unexpected_er(method.toString(), t.toString()).error(); continue; } /* LOG.warn("Fencing method "+method+" was unsuccessful.") */ LOG.fencing_method_was_unsuccessful(method.toString()).warn(); } /* LOG.error("Unable to fence service by any configured method.") */ LOG.unable_fence_service_any_configured_meth().error(); return false; } private static List<FenceMethodWithArg> parseMethods(Configuration conf, String spec) throws BadFencingConfigurationException { String[] lines = spec.split("\\s*\n\\s*"); List<FenceMethodWithArg> methods = Lists.newArrayList(); for (String line : lines) { line = HASH_COMMENT_RE.matcher(line).replaceAll(""); line = line.trim(); if (!line.isEmpty()) { methods.add(parseMethod(conf, line)); } } return methods; } private static FenceMethodWithArg parseMethod(Configuration conf, String line) throws BadFencingConfigurationException { Matcher m; if ((m = CLASS_WITH_ARGUMENT.matcher(line)).matches()) { String className = m.group(1); String arg = m.group(2); return createFenceMethod(conf, className, arg); } else if ((m = CLASS_WITHOUT_ARGUMENT.matcher(line)).matches()) { String className = m.group(1); return createFenceMethod(conf, className, null); } else { throw new BadFencingConfigurationException("Unable to parse line: '" + line + "'"); } } private static FenceMethodWithArg createFenceMethod( Configuration conf, String clazzName, String arg) throws BadFencingConfigurationException { Class<?> clazz; try { // See if it's a short name for one of the built-in methods clazz = STANDARD_METHODS.get(clazzName); if (clazz == null) { // Try to instantiate the user's custom method clazz = Class.forName(clazzName); } } catch (Exception e) { throw new BadFencingConfigurationException( "Could not find configured fencing method " + clazzName, e); } // Check that it implements the right interface if (!FenceMethod.class.isAssignableFrom(clazz)) { throw new BadFencingConfigurationException( "Class " + clazzName + " does not implement FenceMethod"); } FenceMethod method = (FenceMethod) ReflectionUtils.newInstance(clazz, conf); method.checkArgs(arg); return new FenceMethodWithArg(method, arg); } private static class FenceMethodWithArg { private final FenceMethod method; private final String arg; private FenceMethodWithArg(FenceMethod method, String arg) { this.method = method; this.arg = arg; } @Override public String toString() { return method.getClass().getCanonicalName() + "(" + arg + ")"; } } }