All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.hadoop.fs.viewfs.InodeTree Maven / Gradle / Ivy

The newest version!
/**
 * 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.hadoop.fs.viewfs; import java.util.Collection; import java.util.Comparator; import java.util.Set; import java.util.TreeSet; import java.util.function.Function; import org.apache.hadoop.util.Preconditions; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileAlreadyExistsException; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.UnsupportedFileSystemException; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * InodeTree implements a mount-table as a tree of inodes. * It is used to implement ViewFs and ViewFileSystem. * In order to use it the caller must subclass it and implement * the abstract methods {@link #getTargetFileSystem(INodeDir)}, etc. * * The mountable is initialized from the config variables as * specified in {@link ViewFs} * * @param is AbstractFileSystem or FileSystem * * The two main methods are * {@link #InodeTree(Configuration, String, URI, boolean)} // constructor * {@link #resolve(String, boolean)} */ @InterfaceAudience.Private @InterfaceStability.Unstable public abstract class InodeTree { private static final Logger LOGGER = LoggerFactory.getLogger(InodeTree.class.getName()); enum ResultKind { INTERNAL_DIR, EXTERNAL_DIR } static final Path SlashPath = new Path("/"); // the root of the mount table private final INode root; // the fallback filesystem private INodeLink rootFallbackLink; // the homedir for this mount table private final String homedirPrefix; private List> mountPoints = new ArrayList>(); private List> regexMountPointList = new ArrayList>(); private final boolean isNestedMountPointSupported; public static class MountPoint { String src; INodeLink target; MountPoint(String srcPath, INodeLink mountLink) { src = srcPath; target = mountLink; } /** * Returns the source of mount point. * @return The source */ public String getSource() { return this.src; } /** * Returns the target INode link. * @return The target INode link */ public INodeLink getTarget() { return this.target; } } /** * Breaks file path into component names. * @param path * @return array of names component names */ static String[] breakIntoPathComponents(final String path) { return path == null ? null : path.split(Path.SEPARATOR); } /** * Internal class for INode tree. * @param */ abstract static class INode { final String fullPath; // the full path to the root public INode(String pathToNode, UserGroupInformation aUgi) { fullPath = pathToNode; } // INode forming the internal mount table directory tree // for ViewFileSystem. This internal directory tree is // constructed based on the mount table config entries // and is read only. abstract boolean isInternalDir(); // INode linking to another filesystem. Represented // via mount table link config entries. boolean isLink() { return !isInternalDir(); } /** * Return the link if isLink. * @return will return null, for non links. */ INodeLink getLink() { return null; } } /** * Internal class to represent an internal dir of the mount table. * @param */ static class INodeDir extends INode { private final Map> children = new HashMap<>(); private T internalDirFs = null; //filesystem of this internal directory private boolean isRoot = false; private INodeLink fallbackLink = null; INodeDir(final String pathToNode, final UserGroupInformation aUgi) { super(pathToNode, aUgi); } @Override boolean isInternalDir() { return true; } T getInternalDirFs() { return internalDirFs; } void setInternalDirFs(T internalDirFs) { this.internalDirFs = internalDirFs; } void setRoot(boolean root) { isRoot = root; } boolean isRoot() { return isRoot; } INodeLink getFallbackLink() { return fallbackLink; } void addFallbackLink(INodeLink link) throws IOException { if (!isRoot) { throw new IOException("Fallback link can only be added for root"); } this.fallbackLink = link; } Map> getChildren() { return Collections.unmodifiableMap(children); } INode resolveInternal(final String pathComponent) { return children.get(pathComponent); } INodeDir addDir(final String pathComponent, final UserGroupInformation aUgi) throws FileAlreadyExistsException { if (children.containsKey(pathComponent)) { throw new FileAlreadyExistsException(); } final INodeDir newDir = new INodeDir(fullPath + (isRoot() ? "" : "/") + pathComponent, aUgi); children.put(pathComponent, newDir); return newDir; } void addLink(final String pathComponent, final INodeLink link) throws FileAlreadyExistsException { if (children.containsKey(pathComponent)) { throw new FileAlreadyExistsException(); } children.put(pathComponent, link); } void addDirLink(final String pathComponent, final INodeDirLink dirLink) { children.put(pathComponent, dirLink); } } /** * Internal class to represent an INodeDir which also contains a INodeLink. This is used to support nested mount points * where an INode is internalDir but points to a mount link. The class is a subclass of INodeDir and the semantics are * as follows: * isLink(): true * isInternalDir(): true * @param */ static class INodeDirLink extends INodeDir { /** * INodeLink wrapped in the INodeDir */ private final INodeLink link; INodeDirLink(String pathToNode, UserGroupInformation aUgi, INodeLink link) { super(pathToNode, aUgi); this.link = link; } @Override INodeLink getLink() { return link; } /** * True because the INodeDirLink also contains a INodeLink */ @Override boolean isLink() { return true; } /** * True because the INodeDirLink is internal node */ @Override boolean isInternalDir() { return true; } } /** * Mount table link type. */ enum LinkType { /** * Link entry pointing to a single filesystem uri. * Config prefix: fs.viewfs.mounttable..link. * Refer: {@link Constants#CONFIG_VIEWFS_LINK} */ SINGLE, /** * Fallback filesystem for the paths not mounted by * any single link entries. * Config prefix: fs.viewfs.mounttable..linkFallback * Refer: {@link Constants#CONFIG_VIEWFS_LINK_FALLBACK} */ SINGLE_FALLBACK, /** * Link entry pointing to an union of two or more filesystem uris. * Config prefix: fs.viewfs.mounttable..linkMerge. * Refer: {@link Constants#CONFIG_VIEWFS_LINK_MERGE} */ MERGE, /** * Link entry for merging mount table's root with the * root of another filesystem. * Config prefix: fs.viewfs.mounttable..linkMergeSlash * Refer: {@link Constants#CONFIG_VIEWFS_LINK_MERGE_SLASH} */ MERGE_SLASH, /** * Link entry to write to multiple filesystems and read * from the closest filesystem. * Config prefix: fs.viewfs.mounttable..linkNfly * Refer: {@link Constants#CONFIG_VIEWFS_LINK_NFLY} */ NFLY, /** * Link entry which source are regex exrepssions and target refer matched * group from source * Config prefix: fs.viewfs.mounttable..linkRegex * Refer: {@link Constants#CONFIG_VIEWFS_LINK_REGEX} */ REGEX; } /** * An internal class to represent a mount link. * A mount link can be single dir link or a merge dir link. * A merge dir link is a merge (junction) of links to dirs: * example : merge of 2 dirs * /users -> hdfs:nn1//users * /users -> hdfs:nn2//users * * For a merge, each target is checked to be dir when created but if target * is changed later it is then ignored (a dir with null entries) */ public static class INodeLink extends INode { final String[] targetDirLinkList; private T targetFileSystem; // file system object created from the link. // Function to initialize file system. Only applicable for simple links private Function fileSystemInitMethod; private final Object lock = new Object(); /** * Construct a mergeLink or nfly. */ INodeLink(final String pathToNode, final UserGroupInformation aUgi, final T targetMergeFs, final String[] aTargetDirLinkList) { super(pathToNode, aUgi); targetFileSystem = targetMergeFs; targetDirLinkList = aTargetDirLinkList; } /** * Construct a simple link (i.e. not a mergeLink). */ INodeLink(final String pathToNode, final UserGroupInformation aUgi, Function createFileSystemMethod, final String aTargetDirLink) throws URISyntaxException { super(pathToNode, aUgi); targetFileSystem = null; targetDirLinkList = new String[1]; targetDirLinkList[0] = new URI(aTargetDirLink).toString(); this.fileSystemInitMethod = createFileSystemMethod; } /** * Get the target of the link. If a merge link then it returned * as "," separated URI list. * * @return the path. */ public Path getTargetLink() { StringBuilder result = new StringBuilder(targetDirLinkList[0].toString()); // If merge link, use "," as separator between the merged URIs for (int i = 1; i < targetDirLinkList.length; ++i) { result.append(',').append(targetDirLinkList[i].toString()); } return new Path(result.toString()); } @Override boolean isInternalDir() { return false; } @Override INodeLink getLink() { return this; } /** * Get the instance of FileSystem to use, creating one if needed. * @return An Initialized instance of T * @throws IOException raised on errors performing I/O. */ public T getTargetFileSystem() throws IOException { if (targetFileSystem != null) { return targetFileSystem; } // For non NFLY and MERGE links, we initialize the FileSystem when the // corresponding mount path is accessed. if (targetDirLinkList.length == 1) { synchronized (lock) { if (targetFileSystem != null) { return targetFileSystem; } targetFileSystem = fileSystemInitMethod.apply(URI.create(targetDirLinkList[0])); if (targetFileSystem == null) { throw new IOException( "Could not initialize target File System for URI : " + targetDirLinkList[0]); } } } return targetFileSystem; } T getTargetFileSystemForClose() throws IOException { return targetFileSystem; } } private void createLink(final String src, final String target, final LinkType linkType, final String settings, final UserGroupInformation aUgi, final Configuration config) throws URISyntaxException, IOException, FileAlreadyExistsException, UnsupportedFileSystemException { // Validate that src is valid absolute path final Path srcPath = new Path(src); if (!srcPath.isAbsoluteAndSchemeAuthorityNull()) { throw new IOException("ViewFs: Non absolute mount name in config:" + src); } final String[] srcPaths = breakIntoPathComponents(src); // Make sure root is of INodeDir type before // adding any regular links to it. Preconditions.checkState(root.isInternalDir()); INodeDir curInode = getRootDir(); int i; // Ignore first initial slash, process all except last component for (i = 1; i < srcPaths.length - 1; i++) { final String iPath = srcPaths[i]; INode nextInode = curInode.resolveInternal(iPath); if (nextInode == null) { INodeDir newDir = curInode.addDir(iPath, aUgi); newDir.setInternalDirFs(getTargetFileSystem(newDir)); nextInode = newDir; } if (!nextInode.isInternalDir()) { if (isNestedMountPointSupported) { // nested mount detected, add a new INodeDirLink that wraps existing INodeLink to INodeTree and override existing INodelink INodeDirLink dirLink = new INodeDirLink(nextInode.fullPath, aUgi, (INodeLink) nextInode); curInode.addDirLink(iPath, dirLink); curInode = dirLink; } else { // Error - expected a dir but got a link throw new FileAlreadyExistsException("Path " + nextInode.fullPath + " already exists as link"); } } else { assert(nextInode.isInternalDir()); curInode = (INodeDir) nextInode; } } // Now process the last component // Add the link in 2 cases: does not exist or a link exists String iPath = srcPaths[i];// last component if (curInode.resolveInternal(iPath) != null) { // directory/link already exists StringBuilder strB = new StringBuilder(srcPaths[0]); for (int j = 1; j <= i; ++j) { strB.append('/').append(srcPaths[j]); } throw new FileAlreadyExistsException("Path " + strB + " already exists as dir; cannot create link here"); } final INodeLink newLink; final String fullPath = curInode.fullPath + (curInode == root ? "" : "/") + iPath; switch (linkType) { case SINGLE: newLink = new INodeLink(fullPath, aUgi, initAndGetTargetFs(), target); break; case SINGLE_FALLBACK: case MERGE_SLASH: // Link fallback and link merge slash configuration // are handled specially at InodeTree. throw new IllegalArgumentException("Unexpected linkType: " + linkType); case MERGE: case NFLY: final String[] targetUris = StringUtils.getStrings(target); newLink = new INodeLink(fullPath, aUgi, getTargetFileSystem(settings, StringUtils.stringToURI(targetUris)), targetUris); break; default: throw new IllegalArgumentException(linkType + ": Infeasible linkType"); } curInode.addLink(iPath, newLink); mountPoints.add(new MountPoint(src, newLink)); } /** * The user of this class must subclass and implement the following * 3 abstract methods. * @return Function. */ protected abstract Function initAndGetTargetFs(); protected abstract T getTargetFileSystem(INodeDir dir) throws URISyntaxException, IOException; protected abstract T getTargetFileSystem(String settings, URI[] mergeFsURIs) throws UnsupportedFileSystemException, URISyntaxException, IOException; private INodeDir getRootDir() { Preconditions.checkState(root.isInternalDir()); return (INodeDir)root; } private INodeLink getRootLink() { Preconditions.checkState(!root.isInternalDir()); return (INodeLink)root; } private boolean hasFallbackLink() { return rootFallbackLink != null; } /** * @return true if the root represented as internalDir. In LinkMergeSlash, * there will be root to root mapping. So, root does not represent as * internalDir. */ public boolean isRootInternalDir() { return root.isInternalDir(); } public INodeLink getRootFallbackLink() { Preconditions.checkState(root.isInternalDir()); return rootFallbackLink; } /** * An internal class representing the ViewFileSystem mount table * link entries and their attributes. * @see LinkType */ private static class LinkEntry { private final String src; private final String target; private final LinkType linkType; private final String settings; private final UserGroupInformation ugi; private final Configuration config; LinkEntry(String src, String target, LinkType linkType, String settings, UserGroupInformation ugi, Configuration config) { this.src = src; this.target = target; this.linkType = linkType; this.settings = settings; this.ugi = ugi; this.config = config; } String getSrc() { return src; } String getTarget() { return target; } LinkType getLinkType() { return linkType; } boolean isLinkType(LinkType type) { return this.linkType == type; } String getSettings() { return settings; } UserGroupInformation getUgi() { return ugi; } Configuration getConfig() { return config; } } /** * Create Inode Tree from the specified mount-table specified in Config. * * @param config the mount table keys are prefixed with * FsConstants.CONFIG_VIEWFS_PREFIX. * @param viewName the name of the mount table * if null use defaultMT name. * @param theUri heUri. * @param initingUriAsFallbackOnNoMounts initingUriAsFallbackOnNoMounts. * @throws UnsupportedFileSystemException file system for uri is * not found. * @throws URISyntaxException if the URI does not have an authority * it is badly formed. * @throws FileAlreadyExistsException there is a file at the path specified * or is discovered on one of its ancestors. * @throws IOException raised on errors performing I/O. */ protected InodeTree(final Configuration config, final String viewName, final URI theUri, boolean initingUriAsFallbackOnNoMounts) throws UnsupportedFileSystemException, URISyntaxException, FileAlreadyExistsException, IOException { String mountTableName = viewName; if (mountTableName == null) { mountTableName = ConfigUtil.getDefaultMountTableName(config); } homedirPrefix = ConfigUtil.getHomeDirValue(config, mountTableName); isNestedMountPointSupported = ConfigUtil.isNestedMountPointSupported(config); boolean isMergeSlashConfigured = false; String mergeSlashTarget = null; List linkEntries = new LinkedList<>(); final String mountTablePrefix = Constants.CONFIG_VIEWFS_PREFIX + "." + mountTableName + "."; final String linkPrefix = Constants.CONFIG_VIEWFS_LINK + "."; final String linkFallbackPrefix = Constants.CONFIG_VIEWFS_LINK_FALLBACK; final String linkMergePrefix = Constants.CONFIG_VIEWFS_LINK_MERGE + "."; final String linkMergeSlashPrefix = Constants.CONFIG_VIEWFS_LINK_MERGE_SLASH; boolean gotMountTableEntry = false; final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); for (Entry si : config) { final String key = si.getKey(); if (!key.startsWith(mountTablePrefix)) { continue; } gotMountTableEntry = true; LinkType linkType; String src = key.substring(mountTablePrefix.length()); String settings = null; if (src.startsWith(linkPrefix)) { src = src.substring(linkPrefix.length()); if (src.equals(SlashPath.toString())) { throw new UnsupportedFileSystemException("Unexpected mount table " + "link entry '" + key + "'. Use " + Constants.CONFIG_VIEWFS_LINK_MERGE_SLASH + " instead!"); } linkType = LinkType.SINGLE; } else if (src.startsWith(linkFallbackPrefix)) { checkMntEntryKeyEqualsTarget(src, linkFallbackPrefix); linkType = LinkType.SINGLE_FALLBACK; } else if (src.startsWith(linkMergePrefix)) { // A merge link src = src.substring(linkMergePrefix.length()); linkType = LinkType.MERGE; } else if (src.startsWith(linkMergeSlashPrefix)) { // This is a LinkMergeSlash entry. This entry should // not have any additional source path. checkMntEntryKeyEqualsTarget(src, linkMergeSlashPrefix); linkType = LinkType.MERGE_SLASH; } else if (src.startsWith(Constants.CONFIG_VIEWFS_LINK_NFLY)) { // prefix.settings.src src = src.substring(Constants.CONFIG_VIEWFS_LINK_NFLY.length() + 1); // settings.src settings = src.substring(0, src.indexOf('.')); // settings // settings.src src = src.substring(settings.length() + 1); // src linkType = LinkType.NFLY; } else if (src.startsWith(Constants.CONFIG_VIEWFS_LINK_REGEX)) { linkEntries.add( buildLinkRegexEntry(config, ugi, src, si.getValue())); continue; } else if (src.startsWith(Constants.CONFIG_VIEWFS_HOMEDIR)) { // ignore - we set home dir from config continue; } else { throw new IOException("ViewFs: Cannot initialize: Invalid entry in " + "Mount table in config: " + src); } final String target = si.getValue(); if (linkType != LinkType.MERGE_SLASH) { if (isMergeSlashConfigured) { throw new IOException("Mount table " + mountTableName + " has already been configured with a merge slash link. " + "A regular link should not be added."); } linkEntries.add( new LinkEntry(src, target, linkType, settings, ugi, config)); } else { if (!linkEntries.isEmpty()) { throw new IOException("Mount table " + mountTableName + " has already been configured with regular links. " + "A merge slash link should not be configured."); } if (isMergeSlashConfigured) { throw new IOException("Mount table " + mountTableName + " has already been configured with a merge slash link. " + "Multiple merge slash links for the same mount table is " + "not allowed."); } isMergeSlashConfigured = true; mergeSlashTarget = target; } } // End of for loop. if (isMergeSlashConfigured) { Preconditions.checkNotNull(mergeSlashTarget); root = new INodeLink(mountTableName, ugi, initAndGetTargetFs(), mergeSlashTarget); mountPoints.add(new MountPoint("/", (INodeLink) root)); rootFallbackLink = null; } else { root = new INodeDir("/", UserGroupInformation.getCurrentUser()); getRootDir().setInternalDirFs(getTargetFileSystem(getRootDir())); getRootDir().setRoot(true); INodeLink fallbackLink = null; for (LinkEntry le : getLinkEntries(linkEntries)) { switch (le.getLinkType()) { case SINGLE_FALLBACK: if (fallbackLink != null) { throw new IOException("Mount table " + mountTableName + " has already been configured with a link fallback. " + "Multiple fallback links for the same mount table is " + "not allowed."); } fallbackLink = new INodeLink(mountTableName, ugi, initAndGetTargetFs(), le.getTarget()); continue; case REGEX: addRegexMountEntry(le); continue; default: createLink(le.getSrc(), le.getTarget(), le.getLinkType(), le.getSettings(), le.getUgi(), le.getConfig()); } } rootFallbackLink = fallbackLink; getRootDir().addFallbackLink(rootFallbackLink); } if (!gotMountTableEntry) { if (!initingUriAsFallbackOnNoMounts) { throw new IOException(new StringBuilder( "ViewFs: Cannot initialize: Empty Mount table in config for ") .append(theUri.getScheme()).append("://").append(mountTableName) .append("/").toString()); } FileSystem.LOG .info("Empty mount table detected for {} and considering itself " + "as a linkFallback.", theUri); rootFallbackLink = new INodeLink(mountTableName, ugi, initAndGetTargetFs(), theUri.toString()); getRootDir().addFallbackLink(rootFallbackLink); } } /** * Get collection of linkEntry. Sort mount point based on alphabetical order of the src paths. * The purpose is to group nested paths(shortest path always comes first) during INodeTree creation. * E.g. /foo is nested with /foo/bar so an INodeDirLink will be created at /foo. * @param linkEntries input linkEntries * @return sorted linkEntries */ private Collection getLinkEntries(List linkEntries) { Set sortedLinkEntries = new TreeSet<>(new Comparator() { @Override public int compare(LinkEntry o1, LinkEntry o2) { if (o1 == null) { return -1; } if (o2 == null) { return 1; } String src1 = o1.getSrc(); String src2= o2.getSrc(); return src1.compareTo(src2); } }); sortedLinkEntries.addAll(linkEntries); return sortedLinkEntries; } private void checkMntEntryKeyEqualsTarget( String mntEntryKey, String targetMntEntryKey) throws IOException { if (!mntEntryKey.equals(targetMntEntryKey)) { throw new IOException("ViewFs: Mount points initialization error." + " Invalid " + targetMntEntryKey + " entry in config: " + mntEntryKey); } } private void addRegexMountEntry(LinkEntry le) throws IOException { LOGGER.info("Add regex mount point:" + le.getSrc() + ", target:" + le.getTarget() + ", interceptor settings:" + le.getSettings()); RegexMountPoint regexMountPoint = new RegexMountPoint( this, le.getSrc(), le.getTarget(), le.getSettings()); regexMountPoint.initialize(); regexMountPointList.add(regexMountPoint); } private LinkEntry buildLinkRegexEntry( Configuration config, UserGroupInformation ugi, String mntEntryStrippedKey, String mntEntryValue) { String linkKeyPath = null; String settings = null; final String linkRegexPrefix = Constants.CONFIG_VIEWFS_LINK_REGEX + "."; // settings#.linkKey String settingsAndLinkKeyPath = mntEntryStrippedKey.substring(linkRegexPrefix.length()); int settingLinkKeySepIndex = settingsAndLinkKeyPath .indexOf(RegexMountPoint.SETTING_SRCREGEX_SEP); if (settingLinkKeySepIndex == -1) { // There's no settings linkKeyPath = settingsAndLinkKeyPath; settings = null; } else { // settings#.linkKey style configuration // settings from settings#.linkKey settings = settingsAndLinkKeyPath.substring(0, settingLinkKeySepIndex); // linkKeyPath linkKeyPath = settingsAndLinkKeyPath.substring( settings.length() + RegexMountPoint.SETTING_SRCREGEX_SEP .length()); } return new LinkEntry( linkKeyPath, mntEntryValue, LinkType.REGEX, settings, ugi, config); } /** * Resolve returns ResolveResult. * The caller can continue the resolution of the remainingPath * in the targetFileSystem. * * If the input pathname leads to link to another file system then * the targetFileSystem is the one denoted by the link (except it is * file system chrooted to link target. * If the input pathname leads to an internal mount-table entry then * the target file system is one that represents the internal inode. */ public static class ResolveResult { final ResultKind kind; final T targetFileSystem; final String resolvedPath; final Path remainingPath; // to resolve in the target FileSystem private final boolean isLastInternalDirLink; ResolveResult(final ResultKind k, final T targetFs, final String resolveP, final Path remainingP, boolean isLastIntenalDirLink) { kind = k; targetFileSystem = targetFs; resolvedPath = resolveP; remainingPath = remainingP; this.isLastInternalDirLink = isLastIntenalDirLink; } // Internal dir path resolution completed within the mount table boolean isInternalDir() { return (kind == ResultKind.INTERNAL_DIR); } // Indicates whether the internal dir path resolution completed at the link // or resolved due to fallback. boolean isLastInternalDirLink() { return this.isLastInternalDirLink; } } /** * Resolve the pathname p relative to root InodeDir. * @param p - input path * @param resolveLastComponent resolveLastComponent. * @return ResolveResult which allows further resolution of the remaining path * @throws IOException raised on errors performing I/O. */ public ResolveResult resolve(final String p, final boolean resolveLastComponent) throws IOException { ResolveResult resolveResult = null; String[] path = breakIntoPathComponents(p); if (path.length <= 1) { // special case for when path is "/" T targetFs = root.isInternalDir() ? getRootDir().getInternalDirFs() : getRootLink().getTargetFileSystem(); resolveResult = new ResolveResult(ResultKind.INTERNAL_DIR, targetFs, root.fullPath, SlashPath, false); return resolveResult; } /** * linkMergeSlash has been configured. The root of this mount table has * been linked to the root directory of a file system. * The first non-slash path component should be name of the mount table. */ if (!root.isInternalDir()) { Path remainingPath; StringBuilder remainingPathStr = new StringBuilder(); // ignore first slash for (int i = 1; i < path.length; i++) { remainingPathStr.append("/").append(path[i]); } remainingPath = new Path(remainingPathStr.toString()); resolveResult = new ResolveResult(ResultKind.EXTERNAL_DIR, getRootLink().getTargetFileSystem(), root.fullPath, remainingPath, true); return resolveResult; } Preconditions.checkState(root.isInternalDir()); INodeDir curInode = getRootDir(); // Try to resolve path in the regex mount point resolveResult = tryResolveInRegexMountpoint(p, resolveLastComponent); if (resolveResult != null) { return resolveResult; } int i; INodeDirLink lastResolvedDirLink = null; int lastResolvedDirLinkIndex = -1; // ignore first slash for (i = 1; i < path.length - (resolveLastComponent ? 0 : 1); i++) { INode nextInode = curInode.resolveInternal(path[i]); if (nextInode == null) { // first resolve to dirlink for nested mount point if (isNestedMountPointSupported && lastResolvedDirLink != null) { return new ResolveResult(ResultKind.EXTERNAL_DIR, lastResolvedDirLink.getLink().getTargetFileSystem(), lastResolvedDirLink.fullPath, getRemainingPath(path, i),true); } if (hasFallbackLink()) { resolveResult = new ResolveResult(ResultKind.EXTERNAL_DIR, getRootFallbackLink().getTargetFileSystem(), root.fullPath, new Path(p), false); return resolveResult; } else { StringBuilder failedAt = new StringBuilder(path[0]); for (int j = 1; j <= i; ++j) { failedAt.append('/').append(path[j]); } throw (new FileNotFoundException( "File/Directory does not exist: " + failedAt.toString())); } } if (!nextInode.isInternalDir()) { final INodeLink link = (INodeLink) nextInode; final Path remainingPath = getRemainingPath(path, i + 1); resolveResult = new ResolveResult(ResultKind.EXTERNAL_DIR, link.getTargetFileSystem(), nextInode.fullPath, remainingPath, true); return resolveResult; } else { curInode = (INodeDir) nextInode; // track last resolved nest mount point. if (isNestedMountPointSupported && nextInode.isLink()) { lastResolvedDirLink = (INodeDirLink) nextInode; lastResolvedDirLinkIndex = i; } } } Path remainingPath; if (isNestedMountPointSupported && lastResolvedDirLink != null) { remainingPath = getRemainingPath(path, lastResolvedDirLinkIndex + 1); resolveResult = new ResolveResult(ResultKind.EXTERNAL_DIR, lastResolvedDirLink.getLink().getTargetFileSystem(), lastResolvedDirLink.fullPath, remainingPath,true); } else { remainingPath = resolveLastComponent ? SlashPath : getRemainingPath(path, i); resolveResult = new ResolveResult(ResultKind.INTERNAL_DIR, curInode.getInternalDirFs(), curInode.fullPath, remainingPath, false); } return resolveResult; } /** * Return remaining path from specified index to the end of the path array. * @param path An array of path components split by slash * @param startIndex the specified start index of the path array * @return remaining path. */ private Path getRemainingPath(String[] path, int startIndex) { Path remainingPath; if (startIndex >= path.length) { remainingPath = SlashPath; } else { StringBuilder remainingPathStr = new StringBuilder(); for (int j = startIndex; j < path.length; j++) { remainingPathStr.append("/").append(path[j]); } remainingPath = new Path(remainingPathStr.toString()); } return remainingPath; } /** * Walk through all regex mount points to see * whether the path match any regex expressions. * E.g. link: ^/user/(?<username>\\w+) => s3://$user.apache.com/_${user} * srcPath: is /user/hadoop/dir1 * resolveLastComponent: true * then return value is s3://hadoop.apache.com/_hadoop * * @param srcPath srcPath. * @param resolveLastComponent resolveLastComponent. * @return ResolveResult. */ protected ResolveResult tryResolveInRegexMountpoint(final String srcPath, final boolean resolveLastComponent) { for (RegexMountPoint regexMountPoint : regexMountPointList) { ResolveResult resolveResult = regexMountPoint.resolve(srcPath, resolveLastComponent); if (resolveResult != null) { return resolveResult; } } return null; } /** * Build resolve result. * Here's an example * Mountpoint: fs.viewfs.mounttable.mt * .linkRegex.replaceresolveddstpath:_:-#.^/user/(??<username>\w+) * Value: /targetTestRoot/$username * Dir path to test: * viewfs://mt/user/hadoop_user1/hadoop_dir1 * Expect path: /targetTestRoot/hadoop-user1/hadoop_dir1 * resolvedPathStr: /user/hadoop_user1 * targetOfResolvedPathStr: /targetTestRoot/hadoop-user1 * remainingPath: /hadoop_dir1 * * @param resultKind resultKind. * @param resolvedPathStr resolvedPathStr. * @param targetOfResolvedPathStr targetOfResolvedPathStr. * @param remainingPath remainingPath. * @return targetFileSystem or null on exceptions. */ protected ResolveResult buildResolveResultForRegexMountPoint( ResultKind resultKind, String resolvedPathStr, String targetOfResolvedPathStr, Path remainingPath) { try { T targetFs = initAndGetTargetFs() .apply(new URI(targetOfResolvedPathStr)); if (targetFs == null) { LOGGER.error(String.format( "Not able to initialize target file system." + " ResultKind:%s, resolvedPathStr:%s," + " targetOfResolvedPathStr:%s, remainingPath:%s," + " will return null.", resultKind, resolvedPathStr, targetOfResolvedPathStr, remainingPath)); return null; } return new ResolveResult(resultKind, targetFs, resolvedPathStr, remainingPath, true); } catch (URISyntaxException uex) { LOGGER.error(String.format( "Got Exception while build resolve result." + " ResultKind:%s, resolvedPathStr:%s," + " targetOfResolvedPathStr:%s, remainingPath:%s," + " will return null.", resultKind, resolvedPathStr, targetOfResolvedPathStr, remainingPath), uex); return null; } } public List> getMountPoints() { return mountPoints; } /** * * @return home dir value from mount table; null if no config value * was found. */ public String getHomeDirPrefixValue() { return homedirPrefix; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy