HADOOP-7783. Add more symlink tests that cover intermediate links. Contributed by Eli Collins

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1204376 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Eli Collins 2011-11-21 07:11:07 +00:00
parent 5760d03c87
commit d4306d4bd1
6 changed files with 138 additions and 46 deletions

View File

@ -1364,6 +1364,8 @@ Release 0.22.0 - Unreleased
HADOOP-7457. Remove out-of-date Chinese language documentation. HADOOP-7457. Remove out-of-date Chinese language documentation.
(Jakob Homan via eli) (Jakob Homan via eli)
HADOOP-7783. Add more symlink tests that cover intermediate links. (eli)
Release 0.21.1 - Unreleased Release 0.21.1 - Unreleased
IMPROVEMENTS IMPROVEMENTS

View File

@ -385,7 +385,7 @@ public String getUriPath(final Path p) {
checkPath(p); checkPath(p);
String s = p.toUri().getPath(); String s = p.toUri().getPath();
if (!isValidName(s)) { if (!isValidName(s)) {
throw new InvalidPathException("Path part " + s + " from URI" + p throw new InvalidPathException("Path part " + s + " from URI " + p
+ " is not a valid filename."); + " is not a valid filename.");
} }
return s; return s;

View File

@ -1092,29 +1092,28 @@ public FileStatus next(final AbstractFileSystem fs, final Path p)
* Return a fully qualified version of the given symlink target if it * Return a fully qualified version of the given symlink target if it
* has no scheme and authority. Partially and fully qualified paths * has no scheme and authority. Partially and fully qualified paths
* are returned unmodified. * are returned unmodified.
* @param linkFS The AbstractFileSystem of link * @param pathFS The AbstractFileSystem of the path
* @param link The path of the symlink * @param pathWithLink Path that contains the symlink
* @param target The symlink's target * @param target The symlink's absolute target
* @return Fully qualified version of the target. * @return Fully qualified version of the target.
*/ */
private Path qualifySymlinkTarget(final AbstractFileSystem linkFS, private Path qualifySymlinkTarget(final AbstractFileSystem pathFS,
Path link, Path target) { Path pathWithLink, Path target) {
/* NB: makeQualified uses link's scheme/authority, if specified, /* NB: makeQualified uses the target's scheme and authority, if
* and the scheme/authority of linkFS, if not. If link does have * specified, and the scheme and authority of pathFS, if not. If
* a scheme and authority they should match those of linkFS since * the path does have a scheme and authority we assert they match
* resolve updates the path and file system of a path that contains * those of pathFS since resolve updates the file system of a path
* links each time a link is encountered. * that contains links each time a link is encountered.
*/ */
final String linkScheme = link.toUri().getScheme(); final String scheme = target.toUri().getScheme();
final String linkAuth = link.toUri().getAuthority(); final String auth = target.toUri().getAuthority();
if (linkScheme != null && linkAuth != null) { if (scheme != null && auth != null) {
assert linkScheme.equals(linkFS.getUri().getScheme()); assert scheme.equals(pathFS.getUri().getScheme());
assert linkAuth.equals(linkFS.getUri().getAuthority()); assert auth.equals(pathFS.getUri().getAuthority());
} }
final boolean justPath = (target.toUri().getScheme() == null && return (scheme == null && auth == null)
target.toUri().getAuthority() == null); ? target.makeQualified(pathFS.getUri(), pathWithLink.getParent())
return justPath ? target.makeQualified(linkFS.getUri(), link.getParent()) : target;
: target;
} }
/** /**
@ -1148,16 +1147,19 @@ public FileStatus next(final AbstractFileSystem fs, final Path p)
} }
/** /**
* Returns the un-interpreted target of the given symbolic link. * Returns the target of the given symbolic link as it was specified
* Transparently resolves all links up to the final path component. * when the link was created. Links in the path leading up to the
* @param f * final path component are resolved transparently.
*
* @param f the path to return the target of
* @return The un-interpreted target of the symbolic link. * @return The un-interpreted target of the symbolic link.
* *
* @throws AccessControlException If access is denied * @throws AccessControlException If access is denied
* @throws FileNotFoundException If path <code>f</code> does not exist * @throws FileNotFoundException If path <code>f</code> does not exist
* @throws UnsupportedFileSystemException If file system for <code>f</code> is * @throws UnsupportedFileSystemException If file system for <code>f</code> is
* not supported * not supported
* @throws IOException If an I/O error occurred * @throws IOException If the given path does not refer to a symlink
* or an I/O error occurred
*/ */
public Path getLinkTarget(final Path f) throws AccessControlException, public Path getLinkTarget(final Path f) throws AccessControlException,
FileNotFoundException, UnsupportedFileSystemException, IOException { FileNotFoundException, UnsupportedFileSystemException, IOException {
@ -1277,7 +1279,7 @@ public FsStatus next(final AbstractFileSystem fs, final Path p)
* getFsStatus, getFileStatus, exists, and listStatus. * getFsStatus, getFileStatus, exists, and listStatus.
* *
* Symlink targets are stored as given to createSymlink, assuming the * Symlink targets are stored as given to createSymlink, assuming the
* underlying file system is capable of storign a fully qualified URI. * underlying file system is capable of storing a fully qualified URI.
* Dangling symlinks are permitted. FileContext supports four types of * Dangling symlinks are permitted. FileContext supports four types of
* symlink targets, and resolves them as follows * symlink targets, and resolves them as follows
* <pre> * <pre>

View File

@ -68,13 +68,14 @@ public Path(Path parent, Path child) {
// Add a slash to parent's path so resolution is compatible with URI's // Add a slash to parent's path so resolution is compatible with URI's
URI parentUri = parent.uri; URI parentUri = parent.uri;
String parentPath = parentUri.getPath(); String parentPath = parentUri.getPath();
if (!(parentPath.equals("/") || parentPath.equals(""))) if (!(parentPath.equals("/") || parentPath.equals(""))) {
try { try {
parentUri = new URI(parentUri.getScheme(), parentUri.getAuthority(), parentUri = new URI(parentUri.getScheme(), parentUri.getAuthority(),
parentUri.getPath()+"/", null, parentUri.getFragment()); parentUri.getPath()+"/", null, parentUri.getFragment());
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }
}
URI resolved = parentUri.resolve(child.uri); URI resolved = parentUri.resolve(child.uri);
initialize(resolved.getScheme(), resolved.getAuthority(), initialize(resolved.getScheme(), resolved.getAuthority(),
resolved.getPath(), resolved.getFragment()); resolved.getPath(), resolved.getFragment());
@ -213,7 +214,8 @@ public boolean isUriPathAbsolute() {
* There is some ambiguity here. An absolute path is a slash * There is some ambiguity here. An absolute path is a slash
* relative name without a scheme or an authority. * relative name without a scheme or an authority.
* So either this method was incorrectly named or its * So either this method was incorrectly named or its
* implementation is incorrect. * implementation is incorrect. This method returns true
* even if there is a scheme and authority.
*/ */
public boolean isAbsolute() { public boolean isAbsolute() {
return isUriPathAbsolute(); return isUriPathAbsolute();
@ -307,19 +309,16 @@ public int depth() {
return depth; return depth;
} }
/** /**
* Returns a qualified path object. * Returns a qualified path object.
* *
* Deprecated - use {@link #makeQualified(URI, Path)} * Deprecated - use {@link #makeQualified(URI, Path)}
*/ */
@Deprecated @Deprecated
public Path makeQualified(FileSystem fs) { public Path makeQualified(FileSystem fs) {
return makeQualified(fs.getUri(), fs.getWorkingDirectory()); return makeQualified(fs.getUri(), fs.getWorkingDirectory());
} }
/** Returns a qualified path object. */ /** Returns a qualified path object. */
@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"}) @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
public Path makeQualified(URI defaultUri, Path workingDir ) { public Path makeQualified(URI defaultUri, Path workingDir ) {

View File

@ -28,8 +28,9 @@
import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FSDataOutputStream;
import static org.apache.hadoop.fs.FileContextTestHelper.*; import static org.apache.hadoop.fs.FileContextTestHelper.*;
import static org.junit.Assert.*;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
import org.junit.Test; import org.junit.Test;
import org.junit.Before; import org.junit.Before;
import org.junit.After; import org.junit.After;
@ -238,6 +239,31 @@ public void testStatLinkToFile() throws IOException {
assertFalse(isDir(fc, linkToFile)); assertFalse(isDir(fc, linkToFile));
assertEquals(file.toUri().getPath(), assertEquals(file.toUri().getPath(),
fc.getLinkTarget(linkToFile).toString()); fc.getLinkTarget(linkToFile).toString());
// The local file system does not fully resolve the link
// when obtaining the file status
if (!"file".equals(getScheme())) {
assertEquals(fc.getFileStatus(file), fc.getFileStatus(linkToFile));
assertEquals(fc.makeQualified(file),
fc.getFileStatus(linkToFile).getPath());
assertEquals(fc.makeQualified(linkToFile),
fc.getFileLinkStatus(linkToFile).getPath());
}
}
@Test
/** Stat a relative link to a file */
public void testStatRelLinkToFile() throws IOException {
assumeTrue(!"file".equals(getScheme()));
Path baseDir = new Path(testBaseDir1());
Path file = new Path(testBaseDir1(), "file");
Path linkToFile = new Path(testBaseDir1(), "linkToFile");
createAndWriteFile(file);
fc.createSymlink(new Path("file"), linkToFile, false);
assertEquals(fc.getFileStatus(file), fc.getFileStatus(linkToFile));
assertEquals(fc.makeQualified(file),
fc.getFileStatus(linkToFile).getPath());
assertEquals(fc.makeQualified(linkToFile),
fc.getFileLinkStatus(linkToFile).getPath());
} }
@Test @Test
@ -474,18 +500,15 @@ public void testCreateLinkUsingFullyQualPaths() throws IOException {
* creating using a partially qualified path is file system specific. * creating using a partially qualified path is file system specific.
*/ */
public void testCreateLinkUsingPartQualPath1() throws IOException { public void testCreateLinkUsingPartQualPath1() throws IOException {
// Partially qualified paths are covered for local file systems
// in the previous test.
assumeTrue(!"file".equals(getScheme()));
Path schemeAuth = new Path(testURI().toString()); Path schemeAuth = new Path(testURI().toString());
Path fileWoHost = new Path(getScheme()+"://"+testBaseDir1()+"/file"); Path fileWoHost = new Path(getScheme()+"://"+testBaseDir1()+"/file");
Path link = new Path(testBaseDir1()+"/linkToFile"); Path link = new Path(testBaseDir1()+"/linkToFile");
Path linkQual = new Path(schemeAuth, testBaseDir1()+"/linkToFile"); Path linkQual = new Path(schemeAuth, testBaseDir1()+"/linkToFile");
// Partially qualified paths are covered for local file systems
// in the previous test.
if ("file".equals(getScheme())) {
return;
}
FileContext localFc = FileContext.getLocalFSFileContext(); FileContext localFc = FileContext.getLocalFSFileContext();
fc.createSymlink(fileWoHost, link, false); fc.createSymlink(fileWoHost, link, false);
// Partially qualified path is stored // Partially qualified path is stored
assertEquals(fileWoHost, fc.getLinkTarget(linkQual)); assertEquals(fileWoHost, fc.getLinkTarget(linkQual));
@ -748,7 +771,7 @@ public void testCreateLinkToDotDot() throws IOException {
} }
@Test @Test
/** Test create symlink to ../foo */ /** Test create symlink to ../file */
public void testCreateLinkToDotDotPrefix() throws IOException { public void testCreateLinkToDotDotPrefix() throws IOException {
Path file = new Path(testBaseDir1(), "file"); Path file = new Path(testBaseDir1(), "file");
Path dir = new Path(testBaseDir1(), "test"); Path dir = new Path(testBaseDir1(), "test");
@ -1205,24 +1228,30 @@ public void testRenameFileWithDestParentSymlink() throws IOException {
} }
@Test @Test
/** Operate on a file using a path with an intermediate symlink */ /**
public void testAccessFileViaSymlink() throws IOException { * Create, write, read, append, rename, get the block locations,
* checksums, and delete a file using a path with a symlink as an
* intermediate path component where the link target was specified
* using an absolute path. Rename is covered in more depth below.
*/
public void testAccessFileViaInterSymlinkAbsTarget() throws IOException {
Path baseDir = new Path(testBaseDir1()); Path baseDir = new Path(testBaseDir1());
Path file = new Path(testBaseDir1(), "file");
Path fileNew = new Path(baseDir, "fileNew"); Path fileNew = new Path(baseDir, "fileNew");
Path linkToDir = new Path(testBaseDir2(), "linkToDir"); Path linkToDir = new Path(testBaseDir2(), "linkToDir");
Path fileViaLink = new Path(linkToDir, "file"); Path fileViaLink = new Path(linkToDir, "file");
Path fileNewViaLink = new Path(linkToDir, "fileNew"); Path fileNewViaLink = new Path(linkToDir, "fileNew");
fc.createSymlink(baseDir, linkToDir, false); fc.createSymlink(baseDir, linkToDir, false);
// Create, write, read, append, rename, get block locations and
// checksums, and delete a file using a path that contains a
// symlink as an intermediate path component. Rename is covered
// in more depth below.
createAndWriteFile(fileViaLink); createAndWriteFile(fileViaLink);
assertTrue(exists(fc, fileViaLink)); assertTrue(exists(fc, fileViaLink));
assertTrue(isFile(fc, fileViaLink)); assertTrue(isFile(fc, fileViaLink));
assertFalse(isDir(fc, fileViaLink)); assertFalse(isDir(fc, fileViaLink));
assertFalse(fc.getFileLinkStatus(fileViaLink).isSymlink()); assertFalse(fc.getFileLinkStatus(fileViaLink).isSymlink());
assertFalse(isDir(fc, fileViaLink)); assertFalse(isDir(fc, fileViaLink));
assertEquals(fc.getFileStatus(file),
fc.getFileLinkStatus(file));
assertEquals(fc.getFileStatus(fileViaLink),
fc.getFileLinkStatus(fileViaLink));
readFile(fileViaLink); readFile(fileViaLink);
appendToFile(fileViaLink); appendToFile(fileViaLink);
fc.rename(fileViaLink, fileNewViaLink); fc.rename(fileViaLink, fileNewViaLink);
@ -1237,6 +1266,58 @@ public void testAccessFileViaSymlink() throws IOException {
assertFalse(exists(fc, fileNewViaLink)); assertFalse(exists(fc, fileNewViaLink));
} }
@Test
/**
* Operate on a file using a path with an intermediate symlink where
* the link target was specified as a fully qualified path.
*/
public void testAccessFileViaInterSymlinkQualTarget() throws IOException {
Path baseDir = new Path(testBaseDir1());
Path file = new Path(testBaseDir1(), "file");
Path fileNew = new Path(baseDir, "fileNew");
Path linkToDir = new Path(testBaseDir2(), "linkToDir");
Path fileViaLink = new Path(linkToDir, "file");
Path fileNewViaLink = new Path(linkToDir, "fileNew");
fc.createSymlink(fc.makeQualified(baseDir), linkToDir, false);
createAndWriteFile(fileViaLink);
assertEquals(fc.getFileStatus(file),
fc.getFileLinkStatus(file));
assertEquals(fc.getFileStatus(fileViaLink),
fc.getFileLinkStatus(fileViaLink));
readFile(fileViaLink);
}
@Test
/**
* Operate on a file using a path with an intermediate symlink where
* the link target was specified as a relative path.
*/
public void testAccessFileViaInterSymlinkRelTarget() throws IOException {
assumeTrue(!"file".equals(getScheme()));
Path baseDir = new Path(testBaseDir1());
Path dir = new Path(testBaseDir1(), "dir");
Path file = new Path(dir, "file");
Path linkToDir = new Path(testBaseDir1(), "linkToDir");
Path fileViaLink = new Path(linkToDir, "file");
fc.mkdir(dir, FileContext.DEFAULT_PERM, false);
fc.createSymlink(new Path("dir"), linkToDir, false);
createAndWriteFile(fileViaLink);
// Note that getFileStatus returns fully qualified paths even
// when called on an absolute path.
assertEquals(fc.makeQualified(file),
fc.getFileStatus(file).getPath());
// In each case getFileLinkStatus returns the same FileStatus
// as getFileStatus since we're not calling it on a link and
// FileStatus objects are compared by Path.
assertEquals(fc.getFileStatus(file),
fc.getFileLinkStatus(file));
assertEquals(fc.getFileStatus(fileViaLink),
fc.getFileLinkStatus(fileViaLink));
assertEquals(fc.getFileStatus(fileViaLink),
fc.getFileLinkStatus(file));
}
@Test @Test
/** Test create, list, and delete a directory through a symlink */ /** Test create, list, and delete a directory through a symlink */
public void testAccessDirViaSymlink() throws IOException { public void testAccessDirViaSymlink() throws IOException {
@ -1272,4 +1353,4 @@ public void testSetTimes() throws IOException {
assertEquals(2, fc.getFileStatus(file).getModificationTime()); assertEquals(2, fc.getFileStatus(file).getModificationTime());
} }
} }
} }

View File

@ -95,6 +95,7 @@ public void testParent() {
assertEquals(new Path("/foo"), new Path("/foo/bar").getParent()); assertEquals(new Path("/foo"), new Path("/foo/bar").getParent());
assertEquals(new Path("foo"), new Path("foo/bar").getParent()); assertEquals(new Path("foo"), new Path("foo/bar").getParent());
assertEquals(new Path("/"), new Path("/foo").getParent()); assertEquals(new Path("/"), new Path("/foo").getParent());
assertEquals(null, new Path("/").getParent());
if (Path.WINDOWS) { if (Path.WINDOWS) {
assertEquals(new Path("c:/"), new Path("c:/foo").getParent()); assertEquals(new Path("c:/"), new Path("c:/foo").getParent());
} }
@ -159,6 +160,13 @@ public void testDots() {
assertEquals(new Path("foo/bar/baz","../../..").toString(), ""); assertEquals(new Path("foo/bar/baz","../../..").toString(), "");
assertEquals(new Path("foo/bar/baz","../../../../..").toString(), "../.."); assertEquals(new Path("foo/bar/baz","../../../../..").toString(), "../..");
} }
/** Test Path objects created from other Path objects */
public void testChildParentResolution() throws URISyntaxException, IOException {
Path parent = new Path("foo1://bar1/baz1");
Path child = new Path("foo2://bar2/baz2");
assertEquals(child, new Path(parent, child));
}
public void testScheme() throws java.io.IOException { public void testScheme() throws java.io.IOException {
assertEquals("foo:/bar", new Path("foo:/","/bar").toString()); assertEquals("foo:/bar", new Path("foo:/","/bar").toString());