Return-Path: Delivered-To: apmail-hadoop-common-commits-archive@www.apache.org Received: (qmail 31245 invoked from network); 8 Mar 2011 04:40:39 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 8 Mar 2011 04:40:39 -0000 Received: (qmail 11534 invoked by uid 500); 8 Mar 2011 04:40:38 -0000 Delivered-To: apmail-hadoop-common-commits-archive@hadoop.apache.org Received: (qmail 11503 invoked by uid 500); 8 Mar 2011 04:40:38 -0000 Mailing-List: contact common-commits-help@hadoop.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: common-dev@hadoop.apache.org Delivered-To: mailing list common-commits@hadoop.apache.org Received: (qmail 11496 invoked by uid 99); 8 Mar 2011 04:40:38 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 08 Mar 2011 04:40:38 +0000 X-ASF-Spam-Status: No, hits=-1998.0 required=5.0 tests=ALL_TRUSTED,FB_GET_MEDS X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 08 Mar 2011 04:40:26 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 0BAA42388A19; Tue, 8 Mar 2011 04:40:05 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1079150 [1/3] - in /hadoop/common/branches/yahoo-merge/src: java/ java/org/apache/hadoop/fs/ java/org/apache/hadoop/fs/viewfs/ test/core/org/apache/hadoop/fs/ test/core/org/apache/hadoop/fs/viewfs/ Date: Tue, 08 Mar 2011 04:40:04 -0000 To: common-commits@hadoop.apache.org From: omalley@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20110308044005.0BAA42388A19@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: omalley Date: Tue Mar 8 04:40:03 2011 New Revision: 1079150 URL: http://svn.apache.org/viewvc?rev=1079150&view=rev Log: commit 6ccd8b0a2322d5f46ea61f9e4da077ccfa3031bf Author: Sanjay Radia Date: Wed Dec 8 12:18:00 2010 -0800 Added ViewFs (Client side mount table) +++ b/YAHOO-CHANGES.txt + Port and extend viewfs (client-side mountables) from Fred + Added: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ChRootedFileSystem.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ChRootedFs.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ConfigUtil.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/Constants.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/InodeTree.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ViewFileSystem.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ViewFs.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ViewFsFileStatus.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/ hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/TestChRootedFileSystem.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/TestChRootedFs.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/TestFSMainOperationsLocalFileSystem.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/TestFcCreateMkdirLocalFs.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/TestFcMainOperationsLocalFs.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/TestFcPermissionsLocalFs.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/TestViewFileSystemLocalFileSystem.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/TestViewFsConfig.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/TestViewFsLocalFs.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/ViewFileSystemBaseTest.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/ViewFileSystemTestSetup.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/ViewFsBaseTest.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/viewfs/ViewFsTestSetup.java Modified: hadoop/common/branches/yahoo-merge/src/java/core-default.xml hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FileContext.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FileSystem.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FilterFileSystem.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FsShell.java hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/Trash.java hadoop/common/branches/yahoo-merge/src/test/core/org/apache/hadoop/fs/FSMainOperationsBaseTest.java Modified: hadoop/common/branches/yahoo-merge/src/java/core-default.xml URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/core-default.xml?rev=1079150&r1=1079149&r2=1079150&view=diff ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/core-default.xml (original) +++ hadoop/common/branches/yahoo-merge/src/java/core-default.xml Tue Mar 8 04:40:03 2011 @@ -245,6 +245,14 @@ The FileSystem for hdfs: uris. + + + fs.viewfs.impl + org.apache.hadoop.fs.viewfs.ViewFileSystem + The FileSystem for view file system for viewfs: uris + (ie client side mount table:). + + fs.AbstractFileSystem.file.impl org.apache.hadoop.fs.local.LocalFs @@ -255,7 +263,15 @@ fs.AbstractFileSystem.hdfs.impl org.apache.hadoop.fs.Hdfs - The FileSystem for hdfs: uris. + The AbstractFileSystem for hdfs: uris. + + + + + fs.AbstractFileSystem.viewfs.impl + org.apache.hadoop.fs.viewfs.ViewFs + The AbstractFileSystem for view file system for viewfs: uris + (ie client side mount table:). Modified: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FileContext.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FileContext.java?rev=1079150&r1=1079149&r2=1079150&view=diff ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FileContext.java (original) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FileContext.java Tue Mar 8 04:40:03 2011 @@ -511,7 +511,15 @@ public final class FileContext { } /** - * + * Return the current user's home directory in this file system. + * The default implementation returns "/user/$USER/". + * @return the home directory + */ + public Path getHomeDirectory() { + return defaultFS.getHomeDirectory(); + } + + /** * @return the umask of this FileContext */ public FsPermission getUMask() { Modified: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FileSystem.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FileSystem.java?rev=1079150&r1=1079149&r2=1079150&view=diff ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FileSystem.java (original) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FileSystem.java Tue Mar 8 04:40:03 2011 @@ -519,6 +519,18 @@ public abstract class FileSystem extends } /** + * Return the fully-qualified path of path f resolving the path + * through any symlinks or mount point + * @param p path to be resolved + * @return fully qualified path + * @throws FileNotFoundException + */ + public Path resolvePath(final Path p) throws FileNotFoundException { + checkPath(p); + return p; // default is to return the path + } + + /** * Opens an FSDataInputStream at the indicated Path. * @param f the file name to open * @param bufferSize the size of the buffer to be used. Modified: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FilterFileSystem.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FilterFileSystem.java?rev=1079150&r1=1079149&r2=1079150&view=diff ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FilterFileSystem.java (original) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FilterFileSystem.java Tue Mar 8 04:40:03 2011 @@ -21,7 +21,6 @@ package org.apache.hadoop.fs; import java.io.*; import java.net.URI; import java.util.EnumSet; -import java.util.Iterator; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; @@ -94,6 +93,10 @@ public class FilterFileSystem extends Fi return fs.getFileBlockLocations(file, start, len); } + @Override + public Path resolvePath(final Path p) throws FileNotFoundException { + return fs.resolvePath(p); + } /** * Opens an FSDataInputStream at the indicated Path. * @param f the file name to open Modified: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FsShell.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FsShell.java?rev=1079150&r1=1079149&r2=1079150&view=diff ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FsShell.java (original) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/FsShell.java Tue Mar 8 04:40:03 2011 @@ -154,9 +154,9 @@ public class FsShell extends Configured * and copy them to the local name. srcf is kept. * When copying multiple files, the destination must be a directory. * Otherwise, IOException is thrown. - * @param argv: arguments - * @param pos: Ignore everything before argv[pos] - * @exception: IOException + * @param argv arguments + * @param pos Ignore everything before argv[pos] + * @throws IOException on error * @see org.apache.hadoop.fs.FileSystem.globStatus */ void copyToLocal(String[]argv, int pos) throws IOException { @@ -289,9 +289,9 @@ public class FsShell extends Configured * Get all the files in the directories that match the source file * pattern and merge and sort them to only one file on local fs * srcf is kept. - * @param srcf: a file pattern specifying source files - * @param dstf: a destination local file/directory - * @exception: IOException + * @param srcf a file pattern specifying source files + * @param dstf a destination local file/directory + * @exception IOException * @see org.apache.hadoop.fs.FileSystem.globStatus */ void copyMergeToLocal(String srcf, Path dst) throws IOException { @@ -306,10 +306,10 @@ public class FsShell extends Configured * * Also adds a string between the files (useful for adding \n * to a text file) - * @param srcf: a file pattern specifying source files - * @param dstf: a destination local file/directory - * @param endline: if an end of line character is added to a text file - * @exception: IOException + * @param srcf a file pattern specifying source files + * @param dstf a destination local file/directory + * @param endline if an end of line character is added to a text file + * @throws IOException on error * @see org.apache.hadoop.fs.FileSystem.globStatus */ void copyMergeToLocal(String srcf, Path dst, boolean endline) throws IOException { @@ -339,8 +339,8 @@ public class FsShell extends Configured /** * Fetch all files that match the file pattern srcf and display * their content on stdout. - * @param srcf: a file pattern specifying source files - * @exception: IOException + * @param srcf a file pattern specifying source files + * @throws IOException on error * @see org.apache.hadoop.fs.FileSystem.globStatus */ void cat(String src, boolean verifyChecksum) throws IOException { @@ -572,9 +572,9 @@ public class FsShell extends Configured /** * Actually set the replication for this file * If it fails either throw IOException or print an error msg - * @param file: a file/directory - * @param newRep: new replication factor - * @throws IOException + * @param file a file/directory + * @param newRep new replication factor + * @throws IOException on error */ private void setFileReplication(Path file, FileSystem srcFs, short newRep, List waitList) throws IOException { @@ -970,7 +970,7 @@ public class FsShell extends Configured * the argvp[] array. * If multiple source files are specified, then the destination * must be a directory. Otherwise, IOException is thrown. - * @exception: IOException + * @throws IOException on error */ private int rename(String argv[], Configuration conf) throws IOException { int i = 0; @@ -1055,7 +1055,7 @@ public class FsShell extends Configured * the argvp[] array. * If multiple source files are specified, then the destination * must be a directory. Otherwise, IOException is thrown. - * @exception: IOException + * @throws IOException on error */ private int copy(String argv[], Configuration conf) throws IOException { int i = 0; @@ -1153,8 +1153,7 @@ public class FsShell extends Configured if(!skipTrash) { try { - Trash trashTmp = new Trash(srcFs, getConf()); - if (trashTmp.moveToTrash(src)) { + if (Trash.moveToAppropriateTrash(srcFs, src, getConf())) { System.out.println("Moved to trash: " + src); return; } Modified: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/Trash.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/Trash.java?rev=1079150&r1=1079149&r2=1079150&view=diff ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/Trash.java (original) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/Trash.java Tue Mar 8 04:40:03 2011 @@ -86,7 +86,26 @@ public class Trash extends Configured { FS_TRASH_INTERVAL_DEFAULT) * MSECS_PER_MINUTE); } - + + /** + * In case of the symlinks or mount points, one has to move the appropriate + * trashbin in the actual volume of the path p being deleted. + * + * Hence we get the file system of the fully-qualified resolved-path and + * then move the path p to the trashbin in that volume, + * @param fs - the filesystem of path p + * @param p - the path being deleted - to be moved to trasg + * @param conf - configuration + * @return false if the item is already in the trash or trash is disabled + * @throws IOException on error + */ + public static boolean moveToAppropriateTrash(FileSystem fs, Path p, + Configuration conf) throws IOException { + Path fullyResolvedPath = fs.resolvePath(p); + Trash trash = new Trash(FileSystem.get(fullyResolvedPath.toUri(), conf), conf); + return trash.moveToTrash(fullyResolvedPath); + } + private Trash(Path home, Configuration conf) throws IOException { super(conf); this.fs = home.getFileSystem(conf); Added: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ChRootedFileSystem.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ChRootedFileSystem.java?rev=1079150&view=auto ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ChRootedFileSystem.java (added) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ChRootedFileSystem.java Tue Mar 8 04:40:03 2011 @@ -0,0 +1,303 @@ +/** + * 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.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.BlockLocation; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.FileChecksum; +import org.apache.hadoop.fs.FileStatus; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.FsServerDefaults; +import org.apache.hadoop.fs.FsStatus; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.util.Progressable; + +/** + * ChRootedFileSystem is a file system with its root some path + * below the root of its base file system. + * + * Example: For a base file system hdfs://nn1/ with chRoot at /usr/foo, the + * members will be setup as shown below. + *
    + *
  • myFs is the base file system and points to hdfs at nn1
  • + *
  • myURI is hdfs://nn1/user/foo
  • + *
  • chRootPathPart is /user/foo
  • + *
  • workingDir is a directory related to chRoot
  • + *
+ * + * The paths are resolved as follows by ChRootedFileSystem: + *
    + *
  • Absolute path /a/b/c is resolved to /user/foo/a/b/c at myFs
  • + *
  • Relative path x/y is resolved to /user/foo//x/y
  • + *
+ */ + +@InterfaceAudience.Private +@InterfaceStability.Evolving /*Evolving for a release,to be changed to Stable */ +class ChRootedFileSystem extends FileSystem { + private final FileSystem myFs; // the base file system whose root is changed + private final URI myUri; // the base URI + the chRoot + private final Path chRootPathPart; // the root below the root of the base + private final String chRootPathPartString; + private Path workingDir; + + protected FileSystem getMyFs() { + return myFs; + } + + /** + * @param path + * @return full path including the chroot + */ + protected Path fullPath(final Path path) { + super.checkPath(path); + return path.isAbsolute() ? + new Path(chRootPathPartString + path.toUri().getPath()) : + new Path(chRootPathPartString + workingDir.toUri().getPath(), path); + } + + /** + * Constructor + * @param fs base file system + * @param theRoot chRoot for this file system + * @throws URISyntaxException + */ + public ChRootedFileSystem(final FileSystem fs, final Path theRoot) + throws URISyntaxException { + myFs = fs; + myFs.makeQualified(theRoot); //check that root is a valid path for fs + // Would like to call myFs.checkPath(theRoot); + // but not public + chRootPathPart = new Path(theRoot.toUri().getPath()); + chRootPathPartString = chRootPathPart.toUri().getPath(); + try { + initialize(fs.getUri(), fs.getConf()); + } catch (IOException e) { // This exception should not be thrown + throw new RuntimeException("This should not occur"); + } + + /* + * We are making URI include the chrootedPath: e.g. file:///chrootedPath. + * This is questionable since Path#makeQualified(uri, path) ignores + * the pathPart of a uri. Since this class is internal we can ignore + * this issue but if we were to make it external then this needs + * to be resolved. + */ + myUri = new URI(myFs.getUri().toString() //+ Path.SEPARATOR + + chRootPathPart.toString().substring(1)); + + workingDir = getHomeDirectory(); + // We don't use the wd of the myFs bu set it to root. + myFs.setWorkingDirectory(chRootPathPart); + } + + /** + * Called after a new FileSystem instance is constructed. + * @param name a uri whose authority section names the host, port, etc. + * for this FileSystem + * @param conf the configuration + */ + public void initialize(final URI name, final Configuration conf) + throws IOException { + myFs.initialize(name, conf); + super.initialize(name, conf); + setConf(conf); + } + + @Override + public URI getUri() { + return myUri; + } + + @Override + public Path makeQualified(final Path path) { + return myFs.makeQualified(path); + // NOT myFs.makeQualified(fullPath(path)); + } + + /** + * Strip out the root from the path. + * @param p - fully qualified path p + * @return - the remaining path without the begining / + * @throws IOException if the p is not prefixed with root + */ + String stripOutRoot(final Path p) throws IOException { + try { + checkPath(p); + } catch (IllegalArgumentException e) { + throw new IOException("Internal Error - path " + p + + " should have been with URI: " + myUri); + } + String pathPart = p.toUri().getPath(); + return (pathPart.length() == chRootPathPartString.length()) ? "" : pathPart + .substring(chRootPathPartString.length() + 1); + } + + @Override + protected Path getInitialWorkingDirectory() { + /* + * 3 choices here: + * null or / or /user/ or strip out the root out of myFs's + * inital wd. + * Only reasonable choice for initialWd for chrooted fds is null + * so that the default rule for wd is applied + */ + return null; + } + + public Path getResolvedQualifiedPath(final Path f) + throws FileNotFoundException { + return myFs.makeQualified( + new Path(chRootPathPartString + f.toUri().toString())); + } + + @Override + public Path getHomeDirectory() { + return new Path("/user/"+System.getProperty("user.name")).makeQualified( + getUri(), null); + } + + @Override + public Path getWorkingDirectory() { + return workingDir; + } + + @Override + public void setWorkingDirectory(final Path new_dir) { + workingDir = new_dir.isAbsolute() ? new_dir : new Path(workingDir, new_dir); + } + + @Override + public FSDataOutputStream create(final Path f, final FsPermission permission, + final boolean overwrite, final int bufferSize, final short replication, + final long blockSize, final Progressable progress) throws IOException { + return myFs.create(fullPath(f), permission, overwrite, bufferSize, + replication, blockSize, progress); + } + + @Override + public boolean delete(final Path f, final boolean recursive) + throws IOException { + return myFs.delete(fullPath(f), recursive); + } + + + @Override + public boolean delete(Path f) throws IOException { + return delete(f, true); + } + + @Override + public BlockLocation[] getFileBlockLocations(final FileStatus fs, final long start, + final long len) throws IOException { + return myFs.getFileBlockLocations( + new ViewFsFileStatus(fs, fullPath(fs.getPath())), start, len); + } + + @Override + public FileChecksum getFileChecksum(final Path f) + throws IOException { + return myFs.getFileChecksum(fullPath(f)); + } + + @Override + public FileStatus getFileStatus(final Path f) + throws IOException { + return myFs.getFileStatus(fullPath(f)); + } + + @Override + public FsStatus getStatus(Path p) throws IOException { + return myFs.getStatus(fullPath(p)); + } + + @Override + public FsServerDefaults getServerDefaults() throws IOException { + return myFs.getServerDefaults(); + } + + @Override + public FileStatus[] listStatus(final Path f) + throws IOException { + return myFs.listStatus(fullPath(f)); + } + + @Override + public boolean mkdirs(final Path f, final FsPermission permission) + throws IOException { + return myFs.mkdirs(fullPath(f), permission); + } + + @Override + public FSDataInputStream open(final Path f, final int bufferSize) + throws IOException { + return myFs.open(fullPath(f), bufferSize); + } + + @Override + public FSDataOutputStream append(final Path f, final int bufferSize, + final Progressable progress) throws IOException { + return myFs.append(fullPath(f), bufferSize, progress); + } + + @Override + public boolean rename(final Path src, final Path dst) throws IOException { + // note fullPath will check that paths are relative to this FileSystem. + // Hence both are in same file system and a rename is valid + return myFs.rename(fullPath(src), fullPath(dst)); + } + + @Override + public void setOwner(final Path f, final String username, + final String groupname) + throws IOException { + myFs.setOwner(fullPath(f), username, groupname); + } + + @Override + public void setPermission(final Path f, final FsPermission permission) + throws IOException { + myFs.setPermission(fullPath(f), permission); + } + + @Override + public boolean setReplication(final Path f, final short replication) + throws IOException { + return myFs.setReplication(fullPath(f), replication); + } + + @Override + public void setTimes(final Path f, final long mtime, final long atime) + throws IOException { + myFs.setTimes(fullPath(f), mtime, atime); + } + + @Override + public void setVerifyChecksum(final boolean verifyChecksum) { + myFs.setVerifyChecksum(verifyChecksum); + } +} Added: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ChRootedFs.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ChRootedFs.java?rev=1079150&view=auto ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ChRootedFs.java (added) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ChRootedFs.java Tue Mar 8 04:40:03 2011 @@ -0,0 +1,287 @@ +/** + * 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.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.EnumSet; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.fs.AbstractFileSystem; +import org.apache.hadoop.fs.BlockLocation; +import org.apache.hadoop.fs.CreateFlag; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.FileChecksum; +import org.apache.hadoop.fs.FileStatus; +import org.apache.hadoop.fs.FsServerDefaults; +import org.apache.hadoop.fs.FsStatus; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.UnresolvedLinkException; +import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.util.Progressable; + +/** + * ChrootedFs is a file system with its root some path + * below the root of its base file system. + * Example: For a base file system hdfs://nn1/ with chRoot at /usr/foo, the + * members will be setup as shown below. + *
    + *
  • myFs is the base file system and points to hdfs at nn1
  • + *
  • myURI is hdfs://nn1/user/foo
  • + *
  • chRootPathPart is /user/foo
  • + *
  • workingDir is a directory related to chRoot
  • + *
+ * + * The paths are resolved as follows by ChRootedFileSystem: + *
    + *
  • Absolute path /a/b/c is resolved to /user/foo/a/b/c at myFs
  • + *
  • Relative path x/y is resolved to /user/foo//x/y
  • + *
+ + * + */ +@InterfaceAudience.Private +@InterfaceStability.Evolving /*Evolving for a release,to be changed to Stable */ +class ChRootedFs extends AbstractFileSystem { + private final AbstractFileSystem myFs; // the base file system whose root is changed + private final URI myUri; // the base URI + the chroot + private final Path chRootPathPart; // the root below the root of the base + private final String chRootPathPartString; + + protected AbstractFileSystem getMyFs() { + return myFs; + } + + /** + * + * @param path + * @return return full path including the chroot + */ + protected Path fullPath(final Path path) { + super.checkPath(path); + return new Path(chRootPathPartString + path.toUri().getPath()); + } + + public ChRootedFs(final AbstractFileSystem fs, final Path theRoot) + throws URISyntaxException { + super(fs.getUri(), fs.getUri().getScheme(), + fs.getUri().getAuthority() != null, fs.getUriDefaultPort()); + myFs = fs; + myFs.checkPath(theRoot); + chRootPathPart = new Path(myFs.getUriPath(theRoot)); + chRootPathPartString = chRootPathPart.toUri().getPath(); + /* + * We are making URI include the chrootedPath: e.g. file:///chrootedPath. + * This is questionable since Path#makeQualified(uri, path) ignores + * the pathPart of a uri. Since this class is internal we can ignore + * this issue but if we were to make it external then this needs + * to be resolved. + */ + myUri = new URI(myFs.getUri().toString() + chRootPathPart.toString().substring(1)); + super.checkPath(theRoot); + } + + @Override + public URI getUri() { + return myUri; + } + + + /** + * + * Strip out the root from the path. + * + * @param p - fully qualified path p + * @return - the remaining path without the begining / + */ + public String stripOutRoot(final Path p) { + try { + checkPath(p); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Internal Error - path " + p + + " should have been with URI" + myUri); + } + String pathPart = p.toUri().getPath(); + return (pathPart.length() == chRootPathPartString.length()) ? + "" : pathPart.substring(chRootPathPartString.length() + 1); + } + + + @Override + public Path getHomeDirectory() { + return myFs.getHomeDirectory(); + } + + @Override + public Path getInitialWorkingDirectory() { + /* + * 3 choices here: return null or / or strip out the root out of myFs's + * inital wd. + * Only reasonable choice for initialWd for chrooted fds is null + */ + return null; + } + + @Override + public FSDataOutputStream createInternal(final Path f, + final EnumSet flag, final FsPermission absolutePermission, + final int bufferSize, final short replication, final long blockSize, + final Progressable progress, final int bytesPerChecksum, + final boolean createParent) throws IOException, UnresolvedLinkException { + return myFs.createInternal(fullPath(f), flag, + absolutePermission, bufferSize, + replication, blockSize, progress, bytesPerChecksum, createParent); + } + + @Override + public boolean delete(final Path f, final boolean recursive) + throws IOException, UnresolvedLinkException { + return myFs.delete(fullPath(f), recursive); + } + + @Override + public BlockLocation[] getFileBlockLocations(final Path f, final long start, + final long len) throws IOException, UnresolvedLinkException { + return myFs.getFileBlockLocations(fullPath(f), start, len); + } + + @Override + public FileChecksum getFileChecksum(final Path f) + throws IOException, UnresolvedLinkException { + return myFs.getFileChecksum(fullPath(f)); + } + + @Override + public FileStatus getFileStatus(final Path f) + throws IOException, UnresolvedLinkException { + return myFs.getFileStatus(fullPath(f)); + } + + @Override + public FileStatus getFileLinkStatus(final Path f) + throws IOException, UnresolvedLinkException { + return myFs.getFileLinkStatus(fullPath(f)); + } + + @Override + public FsStatus getFsStatus() throws IOException { + return myFs.getFsStatus(); + } + + @Override + public FsServerDefaults getServerDefaults() throws IOException { + return myFs.getServerDefaults(); + } + + @Override + public int getUriDefaultPort() { + return myFs.getUriDefaultPort(); + } + + @Override + public FileStatus[] listStatus(final Path f) + throws IOException, UnresolvedLinkException { + return myFs.listStatus(fullPath(f)); + } + + @Override + public void mkdir(final Path dir, final FsPermission permission, + final boolean createParent) throws IOException, UnresolvedLinkException { + myFs.mkdir(fullPath(dir), permission, createParent); + + } + + @Override + public FSDataInputStream open(final Path f, final int bufferSize) + throws IOException, UnresolvedLinkException { + return myFs.open(fullPath(f), bufferSize); + } + + @Override + public void renameInternal(final Path src, final Path dst) + throws IOException, UnresolvedLinkException { + // note fullPath will check that paths are relative to this FileSystem. + // Hence both are in same file system and a rename is valid + myFs.renameInternal(fullPath(src), fullPath(dst)); + } + + @Override + public void renameInternal(final Path src, final Path dst, + final boolean overwrite) + throws IOException, UnresolvedLinkException { + // note fullPath will check that paths are relative to this FileSystem. + // Hence both are in same file system and a rename is valid + myFs.renameInternal(fullPath(src), fullPath(dst), overwrite); + } + + @Override + public void setOwner(final Path f, final String username, + final String groupname) + throws IOException, UnresolvedLinkException { + myFs.setOwner(fullPath(f), username, groupname); + + } + + @Override + public void setPermission(final Path f, final FsPermission permission) + throws IOException, UnresolvedLinkException { + myFs.setPermission(fullPath(f), permission); + } + + @Override + public boolean setReplication(final Path f, final short replication) + throws IOException, UnresolvedLinkException { + return myFs.setReplication(fullPath(f), replication); + } + + @Override + public void setTimes(final Path f, final long mtime, final long atime) + throws IOException, UnresolvedLinkException { + myFs.setTimes(fullPath(f), mtime, atime); + } + + @Override + public void setVerifyChecksum(final boolean verifyChecksum) + throws IOException, UnresolvedLinkException { + myFs.setVerifyChecksum(verifyChecksum); + } + + @Override + public boolean supportsSymlinks() { + return myFs.supportsSymlinks(); + } + + @Override + public void createSymlink(final Path target, final Path link, + final boolean createParent) throws IOException, UnresolvedLinkException { + /* + * We leave the link alone: + * If qualified or link relative then of course it is okay. + * If absolute (ie / relative) then the link has to be resolved + * relative to the changed root. + */ + myFs.createSymlink(fullPath(target), link, createParent); + } + + @Override + public Path getLinkTarget(final Path f) throws IOException { + return myFs.getLinkTarget(fullPath(f)); + } +} Added: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ConfigUtil.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ConfigUtil.java?rev=1079150&view=auto ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ConfigUtil.java (added) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ConfigUtil.java Tue Mar 8 04:40:03 2011 @@ -0,0 +1,70 @@ +/** + * 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.net.URI; + +import org.apache.hadoop.conf.Configuration; + +/** + * Utilities for config variables of the viewFs See {@link ViewFs} + */ +public class ConfigUtil { + /** + * Get the config variable prefix for the specified mount table + * @param mountTableName - the name of the mount table + * @return the config variable prefix for the specified mount table + */ + public static String getConfigViewFsPrefix(final String mountTableName) { + return Constants.CONFIG_VIEWFS_PREFIX + "." + mountTableName; + } + + /** + * Get the config variable prefix for the default mount table + * @return the config variable prefix for the default mount table + */ + public static String getConfigViewFsPrefix() { + return + getConfigViewFsPrefix(Constants.CONFIG_VIEWFS_PREFIX_DEFAULT_MOUNT_TABLE); + } + + /** + * Add a link to the config for the specified mount table + * @param conf - add the link to this conf + * @param mountTableName + * @param src - the src path name + * @param target - the target URI link + */ + public static void addLink(Configuration conf, final String mountTableName, + final String src, final URI target) { + conf.set(getConfigViewFsPrefix(mountTableName) + "." + + Constants.CONFIG_VIEWFS_LINK + "." + src, target.toString()); + } + + /** + * Add a link to the config for the default mount table + * @param conf - add the link to this conf + * @param src - the src path name + * @param target - the target URI link + */ + public static void addLink(final Configuration conf, final String src, + final URI target) { + addLink( conf, Constants.CONFIG_VIEWFS_DEFAULT_MOUNT_TABLE, + src, target); + } +} Added: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/Constants.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/Constants.java?rev=1079150&view=auto ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/Constants.java (added) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/Constants.java Tue Mar 8 04:40:03 2011 @@ -0,0 +1,63 @@ +/** + * 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 org.apache.hadoop.fs.permission.FsPermission; + +/** + * Config variable prefixes for ViewFs - + * see {@link org.apache.hadoop.fs.viewfs.ViewFs} for examples. + * The mount table is specified in the config using these prefixes. + * See {@link org.apache.hadoop.fs.viewfs.ConfigUtil} for convenience lib. + */ +public interface Constants { + /** + * Prefix for the config variable prefix for the ViewFs mount-table + */ + public static final String CONFIG_VIEWFS_PREFIX = "fs.viewfs.mounttable"; + + /** + * Config variable name for the default mount table. + */ + public static final String CONFIG_VIEWFS_DEFAULT_MOUNT_TABLE = "default"; + + /** + * Config variable full prefix for the default mount table. + */ + public static final String CONFIG_VIEWFS_PREFIX_DEFAULT_MOUNT_TABLE = + CONFIG_VIEWFS_PREFIX + "." + CONFIG_VIEWFS_DEFAULT_MOUNT_TABLE; + + /** + * Config variable for specifying a simple link + */ + public static final String CONFIG_VIEWFS_LINK = "link"; + + /** + * Config variable for specifying a merge link + */ + public static final String CONFIG_VIEWFS_LINK_MERGE = "linkMerge"; + + /** + * Config variable for specifying a merge of the root of the mount-table + * with the root of another file system. + */ + public static final String CONFIG_VIEWFS_LINK_MERGE_SLASH = "linkMergeSlash"; + + static public final FsPermission PERMISSION_RRR = + new FsPermission((short) 0444); +} Added: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/InodeTree.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/InodeTree.java?rev=1079150&view=auto ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/InodeTree.java (added) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/InodeTree.java Tue Mar 8 04:40:03 2011 @@ -0,0 +1,421 @@ +/** + * 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.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +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.Path; +import org.apache.hadoop.fs.UnsupportedFileSystemException; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.util.StringUtils; + + +/** + * 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 three main methods are + * {@link #InodeTreel(Configuration)} // constructor + * {@link #InodeTree(Configuration, String)} // constructor + * {@link #resolve(String, boolean)} + */ + +@InterfaceAudience.Private +@InterfaceStability.Unstable +abstract class InodeTree { + static enum ResultKind {isInternalDir, isExternalDir;}; + static final Path SlashPath = new Path("/"); + + final INodeDir root; // the root of the mount table + + /** + * 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; + } + }; + + /** + * Internal class to represent an internal dir of the mount table + * @param + */ + static class INodeDir extends INode { + final Map> children = new HashMap>(); + T InodeDirFs = null; // file system of this internal directory of mountT + boolean isRoot = false; + + INodeDir(final String pathToNode, final UserGroupInformation aUgi) { + super(pathToNode, aUgi); + } + + INode resolve(final String pathComponent) throws FileNotFoundException { + final INode result = resolveInternal(pathComponent); + if (result == null) { + throw new FileNotFoundException(); + } + return result; + } + + INode resolveInternal(final String pathComponent) + throws FileNotFoundException { + 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); + } + } + + /** + * In 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 : 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) + */ + static class INodeLink extends INode { + final boolean isMergeLink; // true if MergeLink + final URI[] targetDirLinkList; + final T targetFileSystem; // file system object created from the link. + + /** + * Construct a mergeLink + */ + INodeLink(final String pathToNode, final UserGroupInformation aUgi, + final T targetMergeFs, final URI[] aTargetDirLinkList) { + super(pathToNode, aUgi); + targetFileSystem = targetMergeFs; + targetDirLinkList = aTargetDirLinkList; + isMergeLink = true; + } + + /** + * Construct a simple link (i.e. not a mergeLink) + */ + INodeLink(final String pathToNode, final UserGroupInformation aUgi, + final T targetFs, final URI aTargetDirLink) { + super(pathToNode, aUgi); + targetFileSystem = targetFs; + targetDirLinkList = new URI[1]; + targetDirLinkList[0] = aTargetDirLink; + isMergeLink = false; + } + + /** + * Get the target of the link + * If a merge link then it returned as "," separated URI list. + */ + Path getTargetLink() { + // is merge link - use "," as separator between the merged URIs + String result = targetDirLinkList[0].toString(); + for (int i=1; i < targetDirLinkList.length; ++i) { + result += "," + targetDirLinkList[0].toString(); + } + return new Path(result); + } + } + + private void createLink(final String src, final String target, + final boolean isLinkMerge, final UserGroupInformation aUgi) + 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); + INodeDir curInode = root; + 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.InodeDirFs = getTargetFileSystem(newDir); + nextInode = newDir; + } + if (nextInode instanceof INodeLink) { + // Error - expected a dir but got a link + throw new FileAlreadyExistsException("Path " + nextInode.fullPath + + " already exists as link"); + } else { + assert(nextInode instanceof INodeDir); + 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 + String path = srcPaths[0]; + for (int j = 1; j <= i; ++j) { + path += "/" + srcPaths[j]; + } + throw new FileAlreadyExistsException("Path " + path + + " already exists as dir; cannot create link here"); + } + + final INodeLink newLink; + final String fullPath = curInode.fullPath + (curInode == root ? "" : "/") + + iPath; + if (isLinkMerge) { // Target is list of URIs + String[] targetsList = StringUtils.getStrings(target); + URI[] targetsListURI = new URI[targetsList.length]; + int k = 0; + for (String itarget : targetsList) { + targetsListURI[k++] = new URI(itarget); + } + newLink = new INodeLink(fullPath, aUgi, + getTargetFileSystem(targetsListURI), targetsListURI); + } else { + newLink = new INodeLink(fullPath, aUgi, + getTargetFileSystem(new URI(target)), new URI(target)); + } + curInode.addLink(iPath, newLink); + } + + /** + * Below the "public" methods of InodeTree + */ + + /** + * The user of this class must subclass and implement the following + * 3 abstract methods. + * @throws IOException + */ + protected abstract T getTargetFileSystem(final URI uri) + throws UnsupportedFileSystemException, URISyntaxException, IOException; + + protected abstract T getTargetFileSystem(final INodeDir dir) + throws URISyntaxException; + + protected abstract T getTargetFileSystem(final URI[] mergeFsURIList) + throws UnsupportedFileSystemException, URISyntaxException; + + /** + * 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 + * @throws UnsupportedFileSystemException + * @throws URISyntaxException + * @throws FileAlreadyExistsException + * @throws IOException + */ + protected InodeTree(final Configuration config, String viewName) + throws UnsupportedFileSystemException, URISyntaxException, + FileAlreadyExistsException, IOException { + if (viewName == null) { + viewName = Constants.CONFIG_VIEWFS_DEFAULT_MOUNT_TABLE; + } + root = new INodeDir("/", UserGroupInformation.getCurrentUser()); + root.InodeDirFs = getTargetFileSystem(root); + root.isRoot = true; + + final String mtPrefix = Constants.CONFIG_VIEWFS_PREFIX + "." + + viewName + "."; + final String linkPrefix = Constants.CONFIG_VIEWFS_LINK + "."; + final String linkMergePrefix = Constants.CONFIG_VIEWFS_LINK_MERGE + "."; + boolean gotMountTableEntry = false; + final UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); + for (Entry si : config) { + final String key = si.getKey(); + if (key.startsWith(mtPrefix)) { + gotMountTableEntry = true; + boolean isMergeLink = false; + String src = key.substring(mtPrefix.length()); + if (src.startsWith(linkPrefix)) { + src = src.substring(linkPrefix.length()); + } else if (src.startsWith(linkMergePrefix)) { // A merge link + isMergeLink = true; + src = src.substring(linkMergePrefix.length()); + } else { + throw new IOException( + "ViewFs: Cannot initialize: Invalid entry in Mount table in config: "+ + src); + } + final String target = si.getValue(); // link or merge link + createLink(src, target, isMergeLink, ugi); + } + } + if (!gotMountTableEntry) { + throw new IOException( + "ViewFs: Cannot initialize: Empty Mount table in 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. + */ + static class ResolveResult { + final ResultKind kind; + final T targetFileSystem; + final String resolvedPath; + final Path remainingPath; // to resolve in the target FileSystem + + ResolveResult(final ResultKind k, final T targetFs, final String resolveP, + final Path remainingP) { + kind = k; + targetFileSystem = targetFs; + resolvedPath = resolveP; + remainingPath = remainingP; + } + + // isInternalDir of path resolution completed within the mount table + boolean isInternalDir() { + return (kind == ResultKind.isInternalDir); + } + } + + /** + * Resolve the pathname p relative to root InodeDir + * @param p - inout path + * @param resolveLastComponent + * @return ResolveResult which allows further resolution of the remaining path + * @throws FileNotFoundException + */ + ResolveResult resolve(final String p, final boolean resolveLastComponent) + throws FileNotFoundException { + // TO DO: - more efficient to not split the path, but simply compare + String[] path = breakIntoPathComponents(p); + if (path.length <= 1) { // special case for when path is "/" + ResolveResult res = + new ResolveResult(ResultKind.isInternalDir, + root.InodeDirFs, root.fullPath, SlashPath); + return res; + } + + INodeDir curInode = root; + int i; + // ignore first slash + for (i = 1; i < path.length - (resolveLastComponent ? 0 : 1); i++) { + INode nextInode = curInode.resolveInternal(path[i]); + if (nextInode == null) { + String failedAt = path[0]; + for ( int j = 1; j <=i; ++j) { + failedAt += "/" + path[j]; + } + throw (new FileNotFoundException(failedAt)); + } + + if (nextInode instanceof INodeLink) { + final INodeLink link = (INodeLink) nextInode; + final Path remainingPath; + if (i >= path.length-1) { + remainingPath = SlashPath; + } else { + String remainingPathStr = "/" + path[i+1]; + for (int j = i+2; j< path.length; ++j) { + remainingPathStr += "/" + path[j]; + } + remainingPath = new Path(remainingPathStr); + } + final ResolveResult res = + new ResolveResult(ResultKind.isExternalDir, + link.targetFileSystem, nextInode.fullPath, remainingPath); + return res; + } else if (nextInode instanceof INodeDir) { + curInode = (INodeDir) nextInode; + } + } + + // We have resolved to an internal dir in mount table. + Path remainingPath; + if (resolveLastComponent) { + remainingPath = SlashPath; + } else { + // note we have taken care of when path is "/" above + // for internal dirs rem-path does not start with / since the lookup + // that follows will do a children.get(remaningPath) and will have to + // strip-out the initial / + String remainingPathStr = "/" + path[i]; + for (int j = i+1; j< path.length; ++j) { + remainingPathStr += "/" + path[j]; + } + remainingPath = new Path(remainingPathStr); + } + final ResolveResult res = + new ResolveResult(ResultKind.isInternalDir, + curInode.InodeDirFs, curInode.fullPath, remainingPath); + return res; + } +} Added: hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ViewFileSystem.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ViewFileSystem.java?rev=1079150&view=auto ============================================================================== --- hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ViewFileSystem.java (added) +++ hadoop/common/branches/yahoo-merge/src/java/org/apache/hadoop/fs/viewfs/ViewFileSystem.java Tue Mar 8 04:40:03 2011 @@ -0,0 +1,626 @@ +/** + * 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 static org.apache.hadoop.fs.viewfs.Constants.PERMISSION_RRR; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.StringTokenizer; +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.BlockLocation; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.FileAlreadyExistsException; +import org.apache.hadoop.fs.FileChecksum; +import org.apache.hadoop.fs.FileStatus; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.FsConstants; +import org.apache.hadoop.fs.InvalidPathException; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.UnsupportedFileSystemException; +import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.fs.viewfs.InodeTree.INode; +import org.apache.hadoop.fs.viewfs.InodeTree.INodeLink; +import org.apache.hadoop.security.AccessControlException; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.util.Progressable; + +/** + * ViewFileSystem (extends the FileSystem interface) implements a client-side + * mount table. Its spec and implementation is identical to {@link ViewFs}. + */ + +@InterfaceAudience.Public +@InterfaceStability.Evolving /*Evolving for a release,to be changed to Stable */ +public class ViewFileSystem extends FileSystem { + static final AccessControlException READONLY_MOUNTABLE = + new AccessControlException( "InternalDir of ViewFileSystem is readonly"); + + final long creationTime; // of the the mount table + final UserGroupInformation ugi; // the user/group of user who created mtable + private Path workingDir; + Configuration config; + InodeTree fsState; // the fs state; ie the mount table + + /** + * Prohibits names which contain a ".", "..", ":" or "/" + */ + private static boolean isValidName(final String src) { + // Check for ".." "." ":" "/" + final StringTokenizer tokens = new StringTokenizer(src, Path.SEPARATOR); + while(tokens.hasMoreTokens()) { + String element = tokens.nextToken(); + if (element.equals("..") || + element.equals(".") || + (element.indexOf(":") >= 0)) { + return false; + } + } + return true; + } + + /** + * Make the path Absolute and get the path-part of a pathname. + * Checks that URI matches this file system + * and that the path-part is a valid name. + * + * @param p path + * @return path-part of the Path p + */ + private String getUriPath(final Path p) { + checkPath(p); + String s = makeAbsolute(p).toUri().getPath(); + if (!isValidName(s)) { + throw new InvalidPathException("Path part " + s + " from URI" + p + + " is not a valid filename."); + } + return s; + } + + private Path makeAbsolute(final Path f) { + return f.isAbsolute() ? f : new Path(workingDir, f); + } + + /** + * This is the constructor with the signature needed by + * {@link FileSystem#createFileSystem(URI, Configuration)} + * + * After this constructor is called initialize() is called. + * @throws IOException + */ + public ViewFileSystem() throws IOException { + ugi = UserGroupInformation.getCurrentUser(); + creationTime = System.currentTimeMillis(); + } + + /** + * Called after a new FileSystem instance is constructed. + * @param theUri a uri whose authority section names the host, port, etc. for + * this FileSystem + * @param conf the configuration + */ + public void initialize(final URI theUri, final Configuration conf) + throws IOException { + super.initialize(theUri, conf); + setConf(conf); + workingDir = + this.makeQualified(new Path("/user/" + ugi.getShortUserName())); + config = conf; + // Now build client side view (i.e. client side mount table) from config. + final String authority = theUri.getAuthority(); + try { + fsState = new InodeTree(conf, authority) { + + @Override + protected + FileSystem getTargetFileSystem(final URI uri) + throws URISyntaxException, IOException { + return new ChRootedFileSystem(FileSystem.get(uri, config), + new Path(uri.getPath())); + } + + @Override + protected + FileSystem getTargetFileSystem(final INodeDir dir) + throws URISyntaxException { + return new InternalDirOfViewFs(dir, creationTime, ugi); + } + + @Override + protected + FileSystem getTargetFileSystem(URI[] mergeFsURIList) + throws URISyntaxException, UnsupportedFileSystemException { + throw new UnsupportedFileSystemException("mergefs not implemented"); + // return MergeFs.createMergeFs(mergeFsURIList, config); + } + }; + } catch (URISyntaxException e) { + throw new IOException("URISyntax exception: " + theUri); + } + + } + + + /** + * Convenience Constructor for apps to call directly + * @param theUri which must be that of ViewFileSystem + * @param conf + * @throws IOException + */ + ViewFileSystem(final URI theUri, final Configuration conf) + throws IOException { + this(); + initialize(FsConstants.VIEWFS_URI, conf); + } + + /** + * Convenience Constructor for apps to call directly + * @param conf + * @throws IOException + */ + public ViewFileSystem(final Configuration conf) throws IOException { + this(FsConstants.VIEWFS_URI, conf); + } + + public Path getTrashCanLocation(final Path f) throws FileNotFoundException { + final InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + return res.isInternalDir() ? null : res.targetFileSystem.getHomeDirectory(); + } + + @Override + public URI getUri() { + return FsConstants.VIEWFS_URI; + } + + /** + * Return the fully-qualified path of path f - ie follow the path + * through the mount point. + * @param f path + * @return resolved fully-qualified path + * @throws FileNotFoundException + */ + public Path getResolvedQualifiedPath(final Path f) + throws FileNotFoundException { + final InodeTree.ResolveResult res; + res = fsState.resolve(getUriPath(f), true); + if (res.isInternalDir()) { + return f; + } + final ChRootedFileSystem targetFs = + (ChRootedFileSystem) res.targetFileSystem; + return targetFs.getResolvedQualifiedPath(res.remainingPath); + } + + @Override + public Path getWorkingDirectory() { + return workingDir; + } + + @Override + public void setWorkingDirectory(final Path new_dir) { + getUriPath(new_dir); // this validates the path + workingDir = makeAbsolute(new_dir); + } + + @Override + public FSDataOutputStream append(final Path f, final int bufferSize, + final Progressable progress) throws IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + return res.targetFileSystem.append(res.remainingPath, bufferSize, progress); + } + + @Override + public FSDataOutputStream create(final Path f, final FsPermission permission, + final boolean overwrite, final int bufferSize, final short replication, + final long blockSize, final Progressable progress) throws IOException { + InodeTree.ResolveResult res; + try { + res = fsState.resolve(getUriPath(f), false); + } catch (FileNotFoundException e) { + throw READONLY_MOUNTABLE; + } + assert(res.remainingPath != null); + return res.targetFileSystem.create(res.remainingPath, permission, + overwrite, bufferSize, replication, blockSize, progress); + } + + + @Override + public boolean delete(final Path f, final boolean recursive) + throws AccessControlException, FileNotFoundException, + IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + // If internal dir or target is a mount link (ie remainingPath is Slash) + if (res.isInternalDir() || res.remainingPath == InodeTree.SlashPath) { + throw READONLY_MOUNTABLE; + } + return res.targetFileSystem.delete(res.remainingPath, recursive); + } + + @Override + public boolean delete(final Path f) + throws AccessControlException, FileNotFoundException, + IOException { + return delete(f, true); + } + + @Override + public BlockLocation[] getFileBlockLocations(FileStatus fs, + long start, long len) throws IOException { + final InodeTree.ResolveResult res = + fsState.resolve(getUriPath(fs.getPath()), true); + return res.targetFileSystem.getFileBlockLocations( + new ViewFsFileStatus(fs, res.remainingPath), start, len); + } + + @Override + public FileChecksum getFileChecksum(final Path f) + throws AccessControlException, FileNotFoundException, + IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + return res.targetFileSystem.getFileChecksum(f); + } + + @Override + public FileStatus getFileStatus(final Path f) throws AccessControlException, + FileNotFoundException, IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + + // FileStatus#getPath is a fully qualified path relative to the root of + // target file system. + // We need to change it to viewfs URI - relative to root of mount table. + + // The implementors of RawLocalFileSystem were trying to be very smart. + // They implement FileStatus#getOwener lazily -- the object + // returned is really a RawLocalFileSystem that expect the + // FileStatus#getPath to be unchanged so that it can get owner when needed. + // Hence we need to interpose a new ViewFileSystemFileStatus that + // works around. + FileStatus status = res.targetFileSystem.getFileStatus(res.remainingPath); + return new ViewFsFileStatus(status, this.makeQualified(f)); + } + + + @Override + public FileStatus[] listStatus(final Path f) throws AccessControlException, + FileNotFoundException, IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + + FileStatus[] statusLst = res.targetFileSystem.listStatus(res.remainingPath); + if (!res.isInternalDir()) { + // We need to change the name in the FileStatus as described in + // {@link #getFileStatus } + ChRootedFileSystem targetFs; + targetFs = (ChRootedFileSystem) res.targetFileSystem; + int i = 0; + for (FileStatus status : statusLst) { + String suffix = targetFs.stripOutRoot(status.getPath()); + statusLst[i++] = new ViewFsFileStatus(status, this.makeQualified( + suffix.length() == 0 ? f : new Path(res.resolvedPath, suffix))); + } + } + return statusLst; + } + + @Override + public boolean mkdirs(final Path dir, final FsPermission permission) + throws IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(dir), false); + return res.targetFileSystem.mkdirs(res.remainingPath, permission); + } + + @Override + public FSDataInputStream open(final Path f, final int bufferSize) + throws AccessControlException, FileNotFoundException, + IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + return res.targetFileSystem.open(res.remainingPath, bufferSize); + } + + + @Override + public boolean rename(final Path src, final Path dst) throws IOException { + // passing resolveLastComponet as false to catch renaming a mount point to + // itself. We need to catch this as an internal operation and fail. + InodeTree.ResolveResult resSrc = + fsState.resolve(getUriPath(src), false); + + if (resSrc.isInternalDir()) { + throw READONLY_MOUNTABLE; + } + + InodeTree.ResolveResult resDst = + fsState.resolve(getUriPath(dst), false); + if (resDst.isInternalDir()) { + throw new AccessControlException( + "Cannot Rename within internal dirs of mount table: it is readOnly"); + } + /** + // Alternate 1: renames within same file system - valid but we disallow + // Alternate 2: (as described in next para - valid but we have disallowed it + // + // Note we compare the URIs. the URIs include the link targets. + // hence we allow renames across mount links as long as the mount links + // point to the same target. + if (!resSrc.targetFileSystem.getUri().equals( + resDst.targetFileSystem.getUri())) { + throw new IOException("Renames across Mount points not supported"); + } + */ + + // + // Alternate 3 : renames ONLY within the the same mount links. + // + if (resSrc.targetFileSystem !=resDst.targetFileSystem) { + throw new IOException("Renames across Mount points not supported"); + } + return resSrc.targetFileSystem.rename(resSrc.remainingPath, + resDst.remainingPath); + } + + @Override + public void setOwner(final Path f, final String username, + final String groupname) throws AccessControlException, + FileNotFoundException, + IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + res.targetFileSystem.setOwner(res.remainingPath, username, groupname); + } + + @Override + public void setPermission(final Path f, final FsPermission permission) + throws AccessControlException, FileNotFoundException, + IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + res.targetFileSystem.setPermission(res.remainingPath, permission); + } + + @Override + public boolean setReplication(final Path f, final short replication) + throws AccessControlException, FileNotFoundException, + IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + return res.targetFileSystem.setReplication(res.remainingPath, replication); + } + + @Override + public void setTimes(final Path f, final long mtime, final long atime) + throws AccessControlException, FileNotFoundException, + IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(f), true); + res.targetFileSystem.setTimes(res.remainingPath, mtime, atime); + } + + @Override + public void setVerifyChecksum(final boolean verifyChecksum) { + // This is a file system level operations, however ViewFileSystem + // points to many file systems. Noop for ViewFileSystem. + } + + /* + * An instance of this class represents an internal dir of the viewFs + * that is internal dir of the mount table. + * It is a read only mount tables and create, mkdir or delete operations + * are not allowed. + * If called on create or mkdir then this target is the parent of the + * directory in which one is trying to create or mkdir; hence + * in this case the path name passed in is the last component. + * Otherwise this target is the end point of the path and hence + * the path name passed in is null. + */ + static class InternalDirOfViewFs extends FileSystem { + final InodeTree.INodeDir theInternalDir; + final long creationTime; // of the the mount table + final UserGroupInformation ugi; // the user/group of user who created mtable + + public InternalDirOfViewFs(final InodeTree.INodeDir dir, + final long cTime, final UserGroupInformation ugi) + throws URISyntaxException { + try { + initialize(FsConstants.VIEWFS_URI, new Configuration()); + } catch (IOException e) { + throw new RuntimeException("Cannot occur"); + } + theInternalDir = dir; + creationTime = cTime; + this.ugi = ugi; + } + + static private void checkPathIsSlash(final Path f) throws IOException { + if (f != InodeTree.SlashPath) { + throw new IOException ( + "Internal implementation error: expected file name to be /" ); + } + } + + @Override + public URI getUri() { + return FsConstants.VIEWFS_URI; + } + + @Override + public Path getWorkingDirectory() { + throw new RuntimeException ( + "Internal impl error: getWorkingDir should not have been called" ); + } + + @Override + public void setWorkingDirectory(final Path new_dir) { + throw new RuntimeException ( + "Internal impl error: getWorkingDir should not have been called" ); + } + + @Override + public FSDataOutputStream append(final Path f, final int bufferSize, + final Progressable progress) throws IOException { + throw READONLY_MOUNTABLE; + } + + @Override + public FSDataOutputStream create(final Path f, + final FsPermission permission, final boolean overwrite, + final int bufferSize, final short replication, final long blockSize, + final Progressable progress) throws AccessControlException { + throw READONLY_MOUNTABLE; + } + + @Override + public boolean delete(final Path f, final boolean recursive) + throws AccessControlException, IOException { + checkPathIsSlash(f); + throw READONLY_MOUNTABLE; + } + + @Override + public boolean delete(final Path f) + throws AccessControlException, IOException { + return delete(f, true); + } + + @Override + public BlockLocation[] getFileBlockLocations(final FileStatus fs, + final long start, final long len) throws + FileNotFoundException, IOException { + checkPathIsSlash(fs.getPath()); + throw new FileNotFoundException("Path points to dir not a file"); + } + + @Override + public FileChecksum getFileChecksum(final Path f) + throws FileNotFoundException, IOException { + checkPathIsSlash(f); + throw new FileNotFoundException("Path points to dir not a file"); + } + + @Override + public FileStatus getFileStatus(Path f) throws IOException { + checkPathIsSlash(f); + return new FileStatus(0, true, 0, 0, creationTime, creationTime, + PERMISSION_RRR, ugi.getUserName(), ugi.getGroupNames()[0], + + new Path(theInternalDir.fullPath).makeQualified( + FsConstants.VIEWFS_URI, null)); + } + + + @Override + public FileStatus[] listStatus(Path f) throws AccessControlException, + FileNotFoundException, IOException { + checkPathIsSlash(f); + FileStatus[] result = new FileStatus[theInternalDir.children.size()]; + int i = 0; + for (Entry> iEntry : + theInternalDir.children.entrySet()) { + INode inode = iEntry.getValue(); + if (inode instanceof INodeLink ) { + INodeLink link = (INodeLink) inode; + + result[i++] = new FileStatus(0, false, 0, 0, + creationTime, creationTime, PERMISSION_RRR, + ugi.getUserName(), ugi.getGroupNames()[0], + link.getTargetLink(), + new Path(inode.fullPath).makeQualified( + FsConstants.VIEWFS_URI, null)); + } else { + result[i++] = new FileStatus(0, true, 0, 0, + creationTime, creationTime, PERMISSION_RRR, + ugi.getUserName(), ugi.getGroupNames()[0], + new Path(inode.fullPath).makeQualified( + FsConstants.VIEWFS_URI, null)); + } + } + return result; + } + + @Override + public boolean mkdirs(Path dir, FsPermission permission) + throws AccessControlException, FileAlreadyExistsException { + if (theInternalDir.isRoot & dir == null) { + throw new FileAlreadyExistsException("/ already exits"); + } + throw READONLY_MOUNTABLE; + } + + @Override + public FSDataInputStream open(Path f, int bufferSize) + throws AccessControlException, FileNotFoundException, IOException { + checkPathIsSlash(f); + throw new FileNotFoundException("Path points to dir not a file"); + } + + @Override + public boolean rename(Path src, Path dst) throws AccessControlException, + IOException { + checkPathIsSlash(src); + checkPathIsSlash(dst); + throw READONLY_MOUNTABLE; + } + + @Override + public void setOwner(Path f, String username, String groupname) + throws AccessControlException, IOException { + checkPathIsSlash(f); + throw READONLY_MOUNTABLE; + } + + @Override + public void setPermission(Path f, FsPermission permission) + throws AccessControlException, IOException { + checkPathIsSlash(f); + throw READONLY_MOUNTABLE; + } + + @Override + public boolean setReplication(Path f, short replication) + throws AccessControlException, IOException { + checkPathIsSlash(f); + throw READONLY_MOUNTABLE; + } + + @Override + public void setTimes(Path f, long mtime, long atime) + throws AccessControlException, IOException { + checkPathIsSlash(f); + throw READONLY_MOUNTABLE; + } + + @Override + public void setVerifyChecksum(boolean verifyChecksum) { + // Noop for viewfs + } + } +}