You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by cm...@apache.org on 2013/07/12 00:36:37 UTC
svn commit: r1502387 -
/hadoop/common/branches/branch-2.1-beta/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/SymlinkBaseTest.java
Author: cmccabe
Date: Thu Jul 11 22:36:37 2013
New Revision: 1502387
URL: http://svn.apache.org/r1502387
Log:
HADOOP-9418. Add symlink support to DistributedFileSystem (Andrew Wang via Colin Patrick McCabe)
Added:
hadoop/common/branches/branch-2.1-beta/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/SymlinkBaseTest.java
Added: hadoop/common/branches/branch-2.1-beta/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/SymlinkBaseTest.java
URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-2.1-beta/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/SymlinkBaseTest.java?rev=1502387&view=auto
==============================================================================
--- hadoop/common/branches/branch-2.1-beta/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/SymlinkBaseTest.java (added)
+++ hadoop/common/branches/branch-2.1-beta/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/SymlinkBaseTest.java Thu Jul 11 22:36:37 2013
@@ -0,0 +1,1373 @@
+/**
+ * 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;
+
+import java.io.*;
+import java.net.URI;
+import java.util.EnumSet;
+import org.apache.hadoop.fs.FileContext;
+import org.apache.hadoop.fs.Options.CreateOpts;
+import org.apache.hadoop.fs.Options.Rename;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.fs.CreateFlag;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.test.GenericTestUtils;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.After;
+
+/**
+ * Base test for symbolic links
+ */
+public abstract class SymlinkBaseTest {
+ static final long seed = 0xDEADBEEFL;
+ static final int blockSize = 8192;
+ static final int fileSize = 16384;
+ static final int numBlocks = fileSize / blockSize;
+
+ protected static FSTestWrapper wrapper;
+
+ abstract protected String getScheme();
+ abstract protected String testBaseDir1() throws IOException;
+ abstract protected String testBaseDir2() throws IOException;
+ abstract protected URI testURI();
+
+ protected IOException unwrapException(IOException e) {
+ return e;
+ }
+
+ protected static void createAndWriteFile(Path p) throws IOException {
+ createAndWriteFile(wrapper, p);
+ }
+
+ protected static void createAndWriteFile(FSTestWrapper wrapper, Path p)
+ throws IOException {
+ wrapper.createFile(p, numBlocks, CreateOpts.createParent(),
+ CreateOpts.repFac((short) 1), CreateOpts.blockSize(blockSize));
+ }
+
+ protected static void readFile(Path p) throws IOException {
+ wrapper.readFile(p, fileSize);
+ }
+
+ protected static void appendToFile(Path p) throws IOException {
+ wrapper.appendToFile(p, numBlocks,
+ CreateOpts.blockSize(blockSize));
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ wrapper.mkdir(new Path(testBaseDir1()), FileContext.DEFAULT_PERM, true);
+ wrapper.mkdir(new Path(testBaseDir2()), FileContext.DEFAULT_PERM, true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ wrapper.delete(new Path(testBaseDir1()), true);
+ wrapper.delete(new Path(testBaseDir2()), true);
+ }
+
+ @Test(timeout=10000)
+ /** The root is not a symlink */
+ public void testStatRoot() throws IOException {
+ assertFalse(wrapper.getFileLinkStatus(new Path("/")).isSymlink());
+ }
+
+ @Test(timeout=10000)
+ /** Test setWorkingDirectory not resolves symlinks */
+ public void testSetWDNotResolvesLinks() throws IOException {
+ Path dir = new Path(testBaseDir1());
+ Path linkToDir = new Path(testBaseDir1()+"/link");
+ wrapper.createSymlink(dir, linkToDir, false);
+ wrapper.setWorkingDirectory(linkToDir);
+ assertEquals(linkToDir.getName(), wrapper.getWorkingDirectory().getName());
+ }
+
+ @Test(timeout=10000)
+ /** Test create a dangling link */
+ public void testCreateDanglingLink() throws IOException {
+ Path file = new Path("/noSuchFile");
+ Path link = new Path(testBaseDir1()+"/link");
+ wrapper.createSymlink(file, link, false);
+ try {
+ wrapper.getFileStatus(link);
+ fail("Got file status of non-existant file");
+ } catch (FileNotFoundException f) {
+ // Expected
+ }
+ wrapper.delete(link, false);
+ }
+
+ @Test(timeout=10000)
+ /** Test create a link to null and empty path */
+ public void testCreateLinkToNullEmpty() throws IOException {
+ Path link = new Path(testBaseDir1()+"/link");
+ try {
+ wrapper.createSymlink(null, link, false);
+ fail("Can't create symlink to null");
+ } catch (java.lang.NullPointerException e) {
+ // Expected, create* with null yields NPEs
+ }
+ try {
+ wrapper.createSymlink(new Path(""), link, false);
+ fail("Can't create symlink to empty string");
+ } catch (java.lang.IllegalArgumentException e) {
+ // Expected, Path("") is invalid
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Create a link with createParent set */
+ public void testCreateLinkCanCreateParent() throws IOException {
+ Path file = new Path(testBaseDir1()+"/file");
+ Path link = new Path(testBaseDir2()+"/linkToFile");
+ createAndWriteFile(file);
+ wrapper.delete(new Path(testBaseDir2()), true);
+ try {
+ wrapper.createSymlink(file, link, false);
+ fail("Created link without first creating parent dir");
+ } catch (IOException x) {
+ // Expected. Need to create testBaseDir2() first.
+ }
+ assertFalse(wrapper.exists(new Path(testBaseDir2())));
+ wrapper.createSymlink(file, link, true);
+ readFile(link);
+ }
+
+ @Test(timeout=10000)
+ /** Try to create a directory given a path that refers to a symlink */
+ public void testMkdirExistingLink() throws IOException {
+ Path dir = new Path(testBaseDir1()+"/link");
+ wrapper.createSymlink(new Path("/doesNotExist"), dir, false);
+ try {
+ wrapper.mkdir(dir, FileContext.DEFAULT_PERM, false);
+ fail("Created a dir where a symlink exists");
+ } catch (FileAlreadyExistsException e) {
+ // Expected. The symlink already exists.
+ } catch (IOException e) {
+ // LocalFs just throws an IOException
+ assertEquals("file", getScheme());
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Try to create a file with parent that is a dangling link */
+ public void testCreateFileViaDanglingLinkParent() throws IOException {
+ Path dir = new Path(testBaseDir1()+"/dangling");
+ Path file = new Path(testBaseDir1()+"/dangling/file");
+ wrapper.createSymlink(new Path("/doesNotExist"), dir, false);
+ FSDataOutputStream out;
+ try {
+ out = wrapper.create(file, EnumSet.of(CreateFlag.CREATE),
+ CreateOpts.repFac((short) 1),
+ CreateOpts.blockSize(blockSize));
+ out.close();
+ fail("Created a link with dangling link parent");
+ } catch (FileNotFoundException e) {
+ // Expected. The parent is dangling.
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Delete a link */
+ public void testDeleteLink() throws IOException {
+ Path file = new Path(testBaseDir1()+"/file");
+ Path link = new Path(testBaseDir1()+"/linkToFile");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, link, false);
+ readFile(link);
+ wrapper.delete(link, false);
+ try {
+ readFile(link);
+ fail("Symlink should have been deleted");
+ } catch (IOException x) {
+ // Expected
+ }
+ // If we deleted the link we can put it back
+ wrapper.createSymlink(file, link, false);
+ }
+
+ @Test(timeout=10000)
+ /** Ensure open resolves symlinks */
+ public void testOpenResolvesLinks() throws IOException {
+ Path file = new Path(testBaseDir1()+"/noSuchFile");
+ Path link = new Path(testBaseDir1()+"/link");
+ wrapper.createSymlink(file, link, false);
+ try {
+ wrapper.open(link);
+ fail("link target does not exist");
+ } catch (FileNotFoundException x) {
+ // Expected
+ }
+ wrapper.delete(link, false);
+ }
+
+ @Test(timeout=10000)
+ /** Stat a link to a file */
+ public void testStatLinkToFile() throws IOException {
+ Path file = new Path(testBaseDir1()+"/file");
+ Path linkToFile = new Path(testBaseDir1()+"/linkToFile");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, linkToFile, false);
+ assertFalse(wrapper.getFileLinkStatus(linkToFile).isDirectory());
+ assertTrue(wrapper.isSymlink(linkToFile));
+ assertTrue(wrapper.isFile(linkToFile));
+ assertFalse(wrapper.isDir(linkToFile));
+ assertEquals(file.toUri().getPath(),
+ wrapper.getLinkTarget(linkToFile).toString());
+ // The local file system does not fully resolve the link
+ // when obtaining the file status
+ if (!"file".equals(getScheme())) {
+ assertEquals(wrapper.getFileStatus(file),
+ wrapper.getFileStatus(linkToFile));
+ assertEquals(wrapper.makeQualified(file),
+ wrapper.getFileStatus(linkToFile).getPath());
+ assertEquals(wrapper.makeQualified(linkToFile),
+ wrapper.getFileLinkStatus(linkToFile).getPath());
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Stat a relative link to a file */
+ public void testStatRelLinkToFile() throws IOException {
+ assumeTrue(!"file".equals(getScheme()));
+ Path file = new Path(testBaseDir1(), "file");
+ Path linkToFile = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file);
+ wrapper.createSymlink(new Path("file"), linkToFile, false);
+ assertEquals(wrapper.getFileStatus(file),
+ wrapper.getFileStatus(linkToFile));
+ assertEquals(wrapper.makeQualified(file),
+ wrapper.getFileStatus(linkToFile).getPath());
+ assertEquals(wrapper.makeQualified(linkToFile),
+ wrapper.getFileLinkStatus(linkToFile).getPath());
+ }
+
+ @Test(timeout=10000)
+ /** Stat a link to a directory */
+ public void testStatLinkToDir() throws IOException {
+ Path dir = new Path(testBaseDir1());
+ Path linkToDir = new Path(testBaseDir1()+"/linkToDir");
+ wrapper.createSymlink(dir, linkToDir, false);
+
+ assertFalse(wrapper.getFileStatus(linkToDir).isSymlink());
+ assertTrue(wrapper.isDir(linkToDir));
+ assertFalse(wrapper.getFileLinkStatus(linkToDir).isDirectory());
+ assertTrue(wrapper.getFileLinkStatus(linkToDir).isSymlink());
+
+ assertFalse(wrapper.isFile(linkToDir));
+ assertTrue(wrapper.isDir(linkToDir));
+
+ assertEquals(dir.toUri().getPath(),
+ wrapper.getLinkTarget(linkToDir).toString());
+ }
+
+ @Test(timeout=10000)
+ /** Stat a dangling link */
+ public void testStatDanglingLink() throws IOException {
+ Path file = new Path("/noSuchFile");
+ Path link = new Path(testBaseDir1()+"/link");
+ wrapper.createSymlink(file, link, false);
+ assertFalse(wrapper.getFileLinkStatus(link).isDirectory());
+ assertTrue(wrapper.getFileLinkStatus(link).isSymlink());
+ }
+
+ @Test(timeout=10000)
+ /** Stat a non-existant file */
+ public void testStatNonExistentFiles() throws IOException {
+ Path fileAbs = new Path("/doesNotExist");
+ try {
+ wrapper.getFileLinkStatus(fileAbs);
+ fail("Got FileStatus for non-existant file");
+ } catch (FileNotFoundException f) {
+ // Expected
+ }
+ try {
+ wrapper.getLinkTarget(fileAbs);
+ fail("Got link target for non-existant file");
+ } catch (FileNotFoundException f) {
+ // Expected
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Test stat'ing a regular file and directory */
+ public void testStatNonLinks() throws IOException {
+ Path dir = new Path(testBaseDir1());
+ Path file = new Path(testBaseDir1()+"/file");
+ createAndWriteFile(file);
+ try {
+ wrapper.getLinkTarget(dir);
+ fail("Lstat'd a non-symlink");
+ } catch (IOException e) {
+ // Expected.
+ }
+ try {
+ wrapper.getLinkTarget(file);
+ fail("Lstat'd a non-symlink");
+ } catch (IOException e) {
+ // Expected.
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Test links that link to each other */
+ public void testRecursiveLinks() throws IOException {
+ Path link1 = new Path(testBaseDir1()+"/link1");
+ Path link2 = new Path(testBaseDir1()+"/link2");
+ wrapper.createSymlink(link1, link2, false);
+ wrapper.createSymlink(link2, link1, false);
+ try {
+ readFile(link1);
+ fail("Read recursive link");
+ } catch (FileNotFoundException f) {
+ // LocalFs throws sub class of IOException, since File.exists
+ // returns false for a link to link.
+ } catch (IOException x) {
+ assertEquals("Possible cyclic loop while following symbolic link "+
+ link1.toString(), x.getMessage());
+ }
+ }
+
+ /* Assert that the given link to a file behaves as expected. */
+ private void checkLink(Path linkAbs, Path expectedTarget, Path targetQual)
+ throws IOException {
+ Path dir = new Path(testBaseDir1());
+ // isFile/Directory
+ assertTrue(wrapper.isFile(linkAbs));
+ assertFalse(wrapper.isDir(linkAbs));
+
+ // Check getFileStatus
+ assertFalse(wrapper.getFileStatus(linkAbs).isSymlink());
+ assertFalse(wrapper.getFileStatus(linkAbs).isDirectory());
+ assertEquals(fileSize, wrapper.getFileStatus(linkAbs).getLen());
+
+ // Check getFileLinkStatus
+ assertTrue(wrapper.isSymlink(linkAbs));
+ assertFalse(wrapper.getFileLinkStatus(linkAbs).isDirectory());
+
+ // Check getSymlink always returns a qualified target, except
+ // when partially qualified paths are used (see tests below).
+ assertEquals(targetQual.toString(),
+ wrapper.getFileLinkStatus(linkAbs).getSymlink().toString());
+ assertEquals(targetQual, wrapper.getFileLinkStatus(linkAbs).getSymlink());
+ // Check that the target is qualified using the file system of the
+ // path used to access the link (if the link target was not specified
+ // fully qualified, in that case we use the link target verbatim).
+ if (!"file".equals(getScheme())) {
+ FileContext localFc = FileContext.getLocalFSFileContext();
+ Path linkQual = new Path(testURI().toString(), linkAbs);
+ assertEquals(targetQual,
+ localFc.getFileLinkStatus(linkQual).getSymlink());
+ }
+
+ // Check getLinkTarget
+ assertEquals(expectedTarget, wrapper.getLinkTarget(linkAbs));
+
+ // Now read using all path types..
+ wrapper.setWorkingDirectory(dir);
+ readFile(new Path("linkToFile"));
+ readFile(linkAbs);
+ // And fully qualified.. (NB: for local fs this is partially qualified)
+ readFile(new Path(testURI().toString(), linkAbs));
+ // And partially qualified..
+ boolean failureExpected = true;
+ // local files are special cased, no authority
+ if ("file".equals(getScheme())) {
+ failureExpected = false;
+ }
+ // FileSystem automatically adds missing authority if scheme matches default
+ else if (wrapper instanceof FileSystemTestWrapper) {
+ failureExpected = false;
+ }
+ try {
+ readFile(new Path(getScheme()+"://"+testBaseDir1()+"/linkToFile"));
+ assertFalse(failureExpected);
+ } catch (Exception e) {
+ if (!failureExpected) {
+ throw new IOException(e);
+ }
+ //assertTrue(failureExpected);
+ }
+
+ // Now read using a different file context (for HDFS at least)
+ if (wrapper instanceof FileContextTestWrapper
+ && !"file".equals(getScheme())) {
+ FSTestWrapper localWrapper = wrapper.getLocalFSWrapper();
+ localWrapper.readFile(new Path(testURI().toString(), linkAbs), fileSize);
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Test creating a symlink using relative paths */
+ public void testCreateLinkUsingRelPaths() throws IOException {
+ Path fileAbs = new Path(testBaseDir1(), "file");
+ Path linkAbs = new Path(testBaseDir1(), "linkToFile");
+ Path schemeAuth = new Path(testURI().toString());
+ Path fileQual = new Path(schemeAuth, testBaseDir1()+"/file");
+ createAndWriteFile(fileAbs);
+
+ wrapper.setWorkingDirectory(new Path(testBaseDir1()));
+ wrapper.createSymlink(new Path("file"), new Path("linkToFile"), false);
+ checkLink(linkAbs, new Path("file"), fileQual);
+
+ // Now rename the link's parent. Because the target was specified
+ // with a relative path the link should still resolve.
+ Path dir1 = new Path(testBaseDir1());
+ Path dir2 = new Path(testBaseDir2());
+ Path linkViaDir2 = new Path(testBaseDir2(), "linkToFile");
+ Path fileViaDir2 = new Path(schemeAuth, testBaseDir2()+"/file");
+ wrapper.rename(dir1, dir2, Rename.OVERWRITE);
+ FileStatus[] stats = wrapper.listStatus(dir2);
+ assertEquals(fileViaDir2,
+ wrapper.getFileLinkStatus(linkViaDir2).getSymlink());
+ readFile(linkViaDir2);
+ }
+
+ @Test(timeout=10000)
+ /** Test creating a symlink using absolute paths */
+ public void testCreateLinkUsingAbsPaths() throws IOException {
+ Path fileAbs = new Path(testBaseDir1()+"/file");
+ Path linkAbs = new Path(testBaseDir1()+"/linkToFile");
+ Path schemeAuth = new Path(testURI().toString());
+ Path fileQual = new Path(schemeAuth, testBaseDir1()+"/file");
+ createAndWriteFile(fileAbs);
+
+ wrapper.createSymlink(fileAbs, linkAbs, false);
+ checkLink(linkAbs, fileAbs, fileQual);
+
+ // Now rename the link's parent. The target doesn't change and
+ // now no longer exists so accessing the link should fail.
+ Path dir1 = new Path(testBaseDir1());
+ Path dir2 = new Path(testBaseDir2());
+ Path linkViaDir2 = new Path(testBaseDir2(), "linkToFile");
+ wrapper.rename(dir1, dir2, Rename.OVERWRITE);
+ assertEquals(fileQual, wrapper.getFileLinkStatus(linkViaDir2).getSymlink());
+ try {
+ readFile(linkViaDir2);
+ fail("The target should not exist");
+ } catch (FileNotFoundException x) {
+ // Expected
+ }
+ }
+
+ @Test(timeout=10000)
+ /**
+ * Test creating a symlink using fully and partially qualified paths.
+ * NB: For local fs this actually tests partially qualified paths,
+ * as they don't support fully qualified paths.
+ */
+ public void testCreateLinkUsingFullyQualPaths() throws IOException {
+ Path fileAbs = new Path(testBaseDir1(), "file");
+ Path linkAbs = new Path(testBaseDir1(), "linkToFile");
+ Path fileQual = new Path(testURI().toString(), fileAbs);
+ Path linkQual = new Path(testURI().toString(), linkAbs);
+ createAndWriteFile(fileAbs);
+
+ wrapper.createSymlink(fileQual, linkQual, false);
+ checkLink(linkAbs,
+ "file".equals(getScheme()) ? fileAbs : fileQual,
+ fileQual);
+
+ // Now rename the link's parent. The target doesn't change and
+ // now no longer exists so accessing the link should fail.
+ Path dir1 = new Path(testBaseDir1());
+ Path dir2 = new Path(testBaseDir2());
+ Path linkViaDir2 = new Path(testBaseDir2(), "linkToFile");
+ wrapper.rename(dir1, dir2, Rename.OVERWRITE);
+ assertEquals(fileQual, wrapper.getFileLinkStatus(linkViaDir2).getSymlink());
+ try {
+ readFile(linkViaDir2);
+ fail("The target should not exist");
+ } catch (FileNotFoundException x) {
+ // Expected
+ }
+ }
+
+ @Test(timeout=10000)
+ /**
+ * Test creating a symlink using partially qualified paths, ie a scheme
+ * but no authority and vice versa. We just test link targets here since
+ * creating using a partially qualified path is file system specific.
+ */
+ 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 fileWoHost = new Path(getScheme()+"://"+testBaseDir1()+"/file");
+ Path link = new Path(testBaseDir1()+"/linkToFile");
+ Path linkQual = new Path(schemeAuth, testBaseDir1()+"/linkToFile");
+ FSTestWrapper localWrapper = wrapper.getLocalFSWrapper();
+
+ wrapper.createSymlink(fileWoHost, link, false);
+ // Partially qualified path is stored
+ assertEquals(fileWoHost, wrapper.getLinkTarget(linkQual));
+ // NB: We do not add an authority
+ assertEquals(fileWoHost.toString(),
+ wrapper.getFileLinkStatus(link).getSymlink().toString());
+ assertEquals(fileWoHost.toString(),
+ wrapper.getFileLinkStatus(linkQual).getSymlink().toString());
+ // Ditto even from another file system
+ if (wrapper instanceof FileContextTestWrapper) {
+ assertEquals(fileWoHost.toString(),
+ localWrapper.getFileLinkStatus(linkQual).getSymlink().toString());
+ }
+ // Same as if we accessed a partially qualified path directly
+ try {
+ readFile(link);
+ fail("DFS requires URIs with schemes have an authority");
+ } catch (java.lang.RuntimeException e) {
+ assertTrue(wrapper instanceof FileContextTestWrapper);
+ // Expected
+ } catch (FileNotFoundException e) {
+ assertTrue(wrapper instanceof FileSystemTestWrapper);
+ GenericTestUtils.assertExceptionContains(
+ "File does not exist: /test1/file", e);
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Same as above but vice versa (authority but no scheme) */
+ public void testCreateLinkUsingPartQualPath2() throws IOException {
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ Path fileWoScheme = new Path("//"+testURI().getAuthority()+
+ testBaseDir1()+"/file");
+ if ("file".equals(getScheme())) {
+ return;
+ }
+ wrapper.createSymlink(fileWoScheme, link, false);
+ assertEquals(fileWoScheme, wrapper.getLinkTarget(link));
+ assertEquals(fileWoScheme.toString(),
+ wrapper.getFileLinkStatus(link).getSymlink().toString());
+ try {
+ readFile(link);
+ fail("Accessed a file with w/o scheme");
+ } catch (IOException e) {
+ // Expected
+ if (wrapper instanceof FileContextTestWrapper) {
+ assertEquals("No AbstractFileSystem for scheme: null", e.getMessage());
+ } else if (wrapper instanceof FileSystemTestWrapper) {
+ assertEquals("No FileSystem for scheme: null", e.getMessage());
+ }
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Lstat and readlink on a normal file and directory */
+ public void testLinkStatusAndTargetWithNonLink() throws IOException {
+ Path schemeAuth = new Path(testURI().toString());
+ Path dir = new Path(testBaseDir1());
+ Path dirQual = new Path(schemeAuth, dir.toString());
+ Path file = new Path(testBaseDir1(), "file");
+ Path fileQual = new Path(schemeAuth, file.toString());
+ createAndWriteFile(file);
+ assertEquals(wrapper.getFileStatus(file), wrapper.getFileLinkStatus(file));
+ assertEquals(wrapper.getFileStatus(dir), wrapper.getFileLinkStatus(dir));
+ try {
+ wrapper.getLinkTarget(file);
+ fail("Get link target on non-link should throw an IOException");
+ } catch (IOException x) {
+ assertEquals("Path "+fileQual+" is not a symbolic link", x.getMessage());
+ }
+ try {
+ wrapper.getLinkTarget(dir);
+ fail("Get link target on non-link should throw an IOException");
+ } catch (IOException x) {
+ assertEquals("Path "+dirQual+" is not a symbolic link", x.getMessage());
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Test create symlink to a directory */
+ public void testCreateLinkToDirectory() throws IOException {
+ Path dir1 = new Path(testBaseDir1());
+ Path file = new Path(testBaseDir1(), "file");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ createAndWriteFile(file);
+ wrapper.createSymlink(dir1, linkToDir, false);
+ assertFalse(wrapper.isFile(linkToDir));
+ assertTrue(wrapper.isDir(linkToDir));
+ assertTrue(wrapper.getFileStatus(linkToDir).isDirectory());
+ assertTrue(wrapper.getFileLinkStatus(linkToDir).isSymlink());
+ }
+
+ @Test(timeout=10000)
+ /** Test create and remove a file through a symlink */
+ public void testCreateFileViaSymlink() throws IOException {
+ Path dir = new Path(testBaseDir1());
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path fileViaLink = new Path(linkToDir, "file");
+ wrapper.createSymlink(dir, linkToDir, false);
+ createAndWriteFile(fileViaLink);
+ assertTrue(wrapper.isFile(fileViaLink));
+ assertFalse(wrapper.isDir(fileViaLink));
+ assertFalse(wrapper.getFileLinkStatus(fileViaLink).isSymlink());
+ assertFalse(wrapper.getFileStatus(fileViaLink).isDirectory());
+ readFile(fileViaLink);
+ wrapper.delete(fileViaLink, true);
+ assertFalse(wrapper.exists(fileViaLink));
+ }
+
+ @Test(timeout=10000)
+ /** Test make and delete directory through a symlink */
+ public void testCreateDirViaSymlink() throws IOException {
+ Path dir1 = new Path(testBaseDir1());
+ Path subDir = new Path(testBaseDir1(), "subDir");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path subDirViaLink = new Path(linkToDir, "subDir");
+ wrapper.createSymlink(dir1, linkToDir, false);
+ wrapper.mkdir(subDirViaLink, FileContext.DEFAULT_PERM, true);
+ assertTrue(wrapper.isDir(subDirViaLink));
+ wrapper.delete(subDirViaLink, false);
+ assertFalse(wrapper.exists(subDirViaLink));
+ assertFalse(wrapper.exists(subDir));
+ }
+
+ @Test(timeout=10000)
+ /** Create symlink through a symlink */
+ public void testCreateLinkViaLink() throws IOException {
+ Path dir1 = new Path(testBaseDir1());
+ Path file = new Path(testBaseDir1(), "file");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path fileViaLink = new Path(linkToDir, "file");
+ Path linkToFile = new Path(linkToDir, "linkToFile");
+ /*
+ * /b2/linkToDir -> /b1
+ * /b2/linkToDir/linkToFile -> /b2/linkToDir/file
+ */
+ createAndWriteFile(file);
+ wrapper.createSymlink(dir1, linkToDir, false);
+ wrapper.createSymlink(fileViaLink, linkToFile, false);
+ assertTrue(wrapper.isFile(linkToFile));
+ assertTrue(wrapper.getFileLinkStatus(linkToFile).isSymlink());
+ readFile(linkToFile);
+ assertEquals(fileSize, wrapper.getFileStatus(linkToFile).getLen());
+ assertEquals(fileViaLink, wrapper.getLinkTarget(linkToFile));
+ }
+
+ @Test(timeout=10000)
+ /** Test create symlink to a directory */
+ public void testListStatusUsingLink() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path link = new Path(testBaseDir1(), "link");
+ createAndWriteFile(file);
+ wrapper.createSymlink(new Path(testBaseDir1()), link, false);
+ // The size of the result is file system dependent, Hdfs is 2 (file
+ // and link) and LocalFs is 3 (file, link, file crc).
+ FileStatus[] stats = wrapper.listStatus(link);
+ assertTrue(stats.length == 2 || stats.length == 3);
+ RemoteIterator<FileStatus> statsItor = wrapper.listStatusIterator(link);
+ int dirLen = 0;
+ while(statsItor.hasNext()) {
+ statsItor.next();
+ dirLen++;
+ }
+ assertTrue(dirLen == 2 || dirLen == 3);
+ }
+
+ @Test(timeout=10000)
+ /** Test create symlink using the same path */
+ public void testCreateLinkTwice() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, link, false);
+ try {
+ wrapper.createSymlink(file, link, false);
+ fail("link already exists");
+ } catch (IOException x) {
+ // Expected
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Test access via a symlink to a symlink */
+ public void testCreateLinkToLink() throws IOException {
+ Path dir1 = new Path(testBaseDir1());
+ Path file = new Path(testBaseDir1(), "file");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path linkToLink = new Path(testBaseDir2(), "linkToLink");
+ Path fileViaLink = new Path(testBaseDir2(), "linkToLink/file");
+ createAndWriteFile(file);
+ wrapper.createSymlink(dir1, linkToDir, false);
+ wrapper.createSymlink(linkToDir, linkToLink, false);
+ assertTrue(wrapper.isFile(fileViaLink));
+ assertFalse(wrapper.isDir(fileViaLink));
+ assertFalse(wrapper.getFileLinkStatus(fileViaLink).isSymlink());
+ assertFalse(wrapper.getFileStatus(fileViaLink).isDirectory());
+ readFile(fileViaLink);
+ }
+
+ @Test(timeout=10000)
+ /** Can not create a file with path that refers to a symlink */
+ public void testCreateFileDirExistingLink() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, link, false);
+ try {
+ createAndWriteFile(link);
+ fail("link already exists");
+ } catch (IOException x) {
+ // Expected
+ }
+ try {
+ wrapper.mkdir(link, FsPermission.getDefault(), false);
+ fail("link already exists");
+ } catch (IOException x) {
+ // Expected
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Test deleting and recreating a symlink */
+ public void testUseLinkAferDeleteLink() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, link, false);
+ wrapper.delete(link, false);
+ try {
+ readFile(link);
+ fail("link was deleted");
+ } catch (IOException x) {
+ // Expected
+ }
+ readFile(file);
+ wrapper.createSymlink(file, link, false);
+ readFile(link);
+ }
+
+ @Test(timeout=10000)
+ /** Test create symlink to . */
+ public void testCreateLinkToDot() throws IOException {
+ Path dir = new Path(testBaseDir1());
+ Path file = new Path(testBaseDir1(), "file");
+ Path link = new Path(testBaseDir1(), "linkToDot");
+ createAndWriteFile(file);
+ wrapper.setWorkingDirectory(dir);
+ try {
+ wrapper.createSymlink(new Path("."), link, false);
+ fail("Created symlink to dot");
+ } catch (IOException x) {
+ // Expected. Path(".") resolves to "" because URI normalizes
+ // the dot away and AbstractFileSystem considers "" invalid.
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Test create symlink to .. */
+ public void testCreateLinkToDotDot() throws IOException {
+ Path file = new Path(testBaseDir1(), "test/file");
+ Path dotDot = new Path(testBaseDir1(), "test/..");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path fileViaLink = new Path(linkToDir, "test/file");
+ // Symlink to .. is not a problem since the .. is squashed early
+ assertEquals(testBaseDir1(), dotDot.toString());
+ createAndWriteFile(file);
+ wrapper.createSymlink(dotDot, linkToDir, false);
+ readFile(fileViaLink);
+ assertEquals(fileSize, wrapper.getFileStatus(fileViaLink).getLen());
+ }
+
+ @Test(timeout=10000)
+ /** Test create symlink to ../file */
+ public void testCreateLinkToDotDotPrefix() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path dir = new Path(testBaseDir1(), "test");
+ Path link = new Path(testBaseDir1(), "test/link");
+ createAndWriteFile(file);
+ wrapper.mkdir(dir, FsPermission.getDefault(), false);
+ wrapper.setWorkingDirectory(dir);
+ wrapper.createSymlink(new Path("../file"), link, false);
+ readFile(link);
+ assertEquals(new Path("../file"), wrapper.getLinkTarget(link));
+ }
+
+ @Test(timeout=10000)
+ /** Test rename file using a path that contains a symlink. The rename should
+ * work as if the path did not contain a symlink */
+ public void testRenameFileViaSymlink() throws IOException {
+ Path dir = new Path(testBaseDir1());
+ Path file = new Path(testBaseDir1(), "file");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path fileViaLink = new Path(linkToDir, "file");
+ Path fileNewViaLink = new Path(linkToDir, "fileNew");
+ createAndWriteFile(file);
+ wrapper.createSymlink(dir, linkToDir, false);
+ wrapper.rename(fileViaLink, fileNewViaLink);
+ assertFalse(wrapper.exists(fileViaLink));
+ assertFalse(wrapper.exists(file));
+ assertTrue(wrapper.exists(fileNewViaLink));
+ }
+
+ @Test(timeout=10000)
+ /** Test rename a file through a symlink but this time only the
+ * destination path has an intermediate symlink. The rename should work
+ * as if the path did not contain a symlink */
+ public void testRenameFileToDestViaSymlink() throws IOException {
+ Path dir = new Path(testBaseDir1());
+ Path file = new Path(testBaseDir1(), "file");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path subDir = new Path(linkToDir, "subDir");
+ createAndWriteFile(file);
+ wrapper.createSymlink(dir, linkToDir, false);
+ wrapper.mkdir(subDir, FileContext.DEFAULT_PERM, false);
+ try {
+ wrapper.rename(file, subDir);
+ fail("Renamed file to a directory");
+ } catch (IOException e) {
+ // Expected. Both must be directories.
+ assertTrue(unwrapException(e) instanceof IOException);
+ }
+ assertTrue(wrapper.exists(file));
+ }
+
+ @Test(timeout=10000)
+ /** Similar tests as the previous ones but rename a directory */
+ public void testRenameDirViaSymlink() throws IOException {
+ Path baseDir = new Path(testBaseDir1());
+ Path dir = new Path(baseDir, "dir");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path dirViaLink = new Path(linkToDir, "dir");
+ Path dirNewViaLink = new Path(linkToDir, "dirNew");
+ wrapper.mkdir(dir, FileContext.DEFAULT_PERM, false);
+ wrapper.createSymlink(baseDir, linkToDir, false);
+ assertTrue(wrapper.exists(dirViaLink));
+ wrapper.rename(dirViaLink, dirNewViaLink);
+ assertFalse(wrapper.exists(dirViaLink));
+ assertFalse(wrapper.exists(dir));
+ assertTrue(wrapper.exists(dirNewViaLink));
+ }
+
+ @Test(timeout=10000)
+ /** Similar tests as the previous ones but rename a symlink */
+ public void testRenameSymlinkViaSymlink() throws IOException {
+ Path baseDir = new Path(testBaseDir1());
+ Path file = new Path(testBaseDir1(), "file");
+ Path link = new Path(testBaseDir1(), "link");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path linkViaLink = new Path(linkToDir, "link");
+ Path linkNewViaLink = new Path(linkToDir, "linkNew");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, link, false);
+ wrapper.createSymlink(baseDir, linkToDir, false);
+ wrapper.rename(linkViaLink, linkNewViaLink);
+ assertFalse(wrapper.exists(linkViaLink));
+ // Check that we didn't rename the link target
+ assertTrue(wrapper.exists(file));
+ assertTrue(wrapper.getFileLinkStatus(linkNewViaLink).isSymlink());
+ readFile(linkNewViaLink);
+ }
+
+ @Test(timeout=10000)
+ /** Test rename a directory to a symlink to a directory */
+ public void testRenameDirToSymlinkToDir() throws IOException {
+ Path dir1 = new Path(testBaseDir1());
+ Path subDir = new Path(testBaseDir2(), "subDir");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ wrapper.mkdir(subDir, FileContext.DEFAULT_PERM, false);
+ wrapper.createSymlink(subDir, linkToDir, false);
+ try {
+ wrapper.rename(dir1, linkToDir, Rename.OVERWRITE);
+ fail("Renamed directory to a symlink");
+ } catch (IOException e) {
+ // Expected. Both must be directories.
+ assertTrue(unwrapException(e) instanceof IOException);
+ }
+ assertTrue(wrapper.exists(dir1));
+ assertTrue(wrapper.exists(linkToDir));
+ }
+
+ @Test(timeout=10000)
+ /** Test rename a directory to a symlink to a file */
+ public void testRenameDirToSymlinkToFile() throws IOException {
+ Path dir1 = new Path(testBaseDir1());
+ Path file = new Path(testBaseDir2(), "file");
+ Path linkToFile = new Path(testBaseDir2(), "linkToFile");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, linkToFile, false);
+ try {
+ wrapper.rename(dir1, linkToFile, Rename.OVERWRITE);
+ fail("Renamed directory to a symlink");
+ } catch (IOException e) {
+ // Expected. Both must be directories.
+ assertTrue(unwrapException(e) instanceof IOException);
+ }
+ assertTrue(wrapper.exists(dir1));
+ assertTrue(wrapper.exists(linkToFile));
+ }
+
+ @Test(timeout=10000)
+ /** Test rename a directory to a dangling symlink */
+ public void testRenameDirToDanglingSymlink() throws IOException {
+ Path dir = new Path(testBaseDir1());
+ Path link = new Path(testBaseDir2(), "linkToFile");
+ wrapper.createSymlink(new Path("/doesNotExist"), link, false);
+ try {
+ wrapper.rename(dir, link, Rename.OVERWRITE);
+ fail("Renamed directory to a symlink");
+ } catch (IOException e) {
+ // Expected. Both must be directories.
+ assertTrue(unwrapException(e) instanceof IOException);
+ }
+ assertTrue(wrapper.exists(dir));
+ assertTrue(wrapper.getFileLinkStatus(link) != null);
+ }
+
+ @Test(timeout=10000)
+ /** Test rename a file to a symlink to a directory */
+ public void testRenameFileToSymlinkToDir() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path subDir = new Path(testBaseDir1(), "subDir");
+ Path link = new Path(testBaseDir1(), "link");
+ wrapper.mkdir(subDir, FileContext.DEFAULT_PERM, false);
+ wrapper.createSymlink(subDir, link, false);
+ createAndWriteFile(file);
+ try {
+ wrapper.rename(file, link);
+ fail("Renamed file to symlink w/o overwrite");
+ } catch (IOException e) {
+ // Expected
+ assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
+ }
+ wrapper.rename(file, link, Rename.OVERWRITE);
+ assertFalse(wrapper.exists(file));
+ assertTrue(wrapper.exists(link));
+ assertTrue(wrapper.isFile(link));
+ assertFalse(wrapper.getFileLinkStatus(link).isSymlink());
+ }
+
+ @Test(timeout=10000)
+ /** Test rename a file to a symlink to a file */
+ public void testRenameFileToSymlinkToFile() throws IOException {
+ Path file1 = new Path(testBaseDir1(), "file1");
+ Path file2 = new Path(testBaseDir1(), "file2");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file1);
+ createAndWriteFile(file2);
+ wrapper.createSymlink(file2, link, false);
+ try {
+ wrapper.rename(file1, link);
+ fail("Renamed file to symlink w/o overwrite");
+ } catch (IOException e) {
+ // Expected
+ assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
+ }
+ wrapper.rename(file1, link, Rename.OVERWRITE);
+ assertFalse(wrapper.exists(file1));
+ assertTrue(wrapper.exists(link));
+ assertTrue(wrapper.isFile(link));
+ assertFalse(wrapper.getFileLinkStatus(link).isSymlink());
+ }
+
+ @Test(timeout=10000)
+ /** Test rename a file to a dangling symlink */
+ public void testRenameFileToDanglingSymlink() throws IOException {
+ /* NB: Local file system doesn't handle dangling links correctly
+ * since File.exists(danglinLink) returns false. */
+ if ("file".equals(getScheme())) {
+ return;
+ }
+ Path file1 = new Path(testBaseDir1(), "file1");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file1);
+ wrapper.createSymlink(new Path("/doesNotExist"), link, false);
+ try {
+ wrapper.rename(file1, link);
+ } catch (IOException e) {
+ // Expected
+ }
+ wrapper.rename(file1, link, Rename.OVERWRITE);
+ assertFalse(wrapper.exists(file1));
+ assertTrue(wrapper.exists(link));
+ assertTrue(wrapper.isFile(link));
+ assertFalse(wrapper.getFileLinkStatus(link).isSymlink());
+ }
+
+ @Test(timeout=10000)
+ /** Rename a symlink to a new non-existant name */
+ public void testRenameSymlinkNonExistantDest() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path link1 = new Path(testBaseDir1(), "linkToFile1");
+ Path link2 = new Path(testBaseDir1(), "linkToFile2");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, link1, false);
+ wrapper.rename(link1, link2);
+ assertTrue(wrapper.getFileLinkStatus(link2).isSymlink());
+ readFile(link2);
+ readFile(file);
+ assertFalse(wrapper.exists(link1));
+ }
+
+ @Test(timeout=10000)
+ /** Rename a symlink to a file that exists */
+ public void testRenameSymlinkToExistingFile() throws IOException {
+ Path file1 = new Path(testBaseDir1(), "file");
+ Path file2 = new Path(testBaseDir1(), "someFile");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file1);
+ createAndWriteFile(file2);
+ wrapper.createSymlink(file2, link, false);
+ try {
+ wrapper.rename(link, file1);
+ fail("Renamed w/o passing overwrite");
+ } catch (IOException e) {
+ // Expected
+ assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
+ }
+ wrapper.rename(link, file1, Rename.OVERWRITE);
+ assertFalse(wrapper.exists(link));
+ assertTrue(wrapper.getFileLinkStatus(file1).isSymlink());
+ assertEquals(file2, wrapper.getLinkTarget(file1));
+ }
+
+ @Test(timeout=10000)
+ /** Rename a symlink to a directory that exists */
+ public void testRenameSymlinkToExistingDir() throws IOException {
+ Path dir1 = new Path(testBaseDir1());
+ Path dir2 = new Path(testBaseDir2());
+ Path subDir = new Path(testBaseDir2(), "subDir");
+ Path link = new Path(testBaseDir1(), "linkToDir");
+ wrapper.createSymlink(dir1, link, false);
+ try {
+ wrapper.rename(link, dir2);
+ fail("Renamed link to a directory");
+ } catch (IOException e) {
+ // Expected. Both must be directories.
+ assertTrue(unwrapException(e) instanceof IOException);
+ }
+ try {
+ wrapper.rename(link, dir2, Rename.OVERWRITE);
+ fail("Renamed link to a directory");
+ } catch (IOException e) {
+ // Expected. Both must be directories.
+ assertTrue(unwrapException(e) instanceof IOException);
+ }
+ // Also fails when dir2 has a sub-directory
+ wrapper.mkdir(subDir, FsPermission.getDefault(), false);
+ try {
+ wrapper.rename(link, dir2, Rename.OVERWRITE);
+ fail("Renamed link to a directory");
+ } catch (IOException e) {
+ // Expected. Both must be directories.
+ assertTrue(unwrapException(e) instanceof IOException);
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Rename a symlink to itself */
+ public void testRenameSymlinkToItself() throws IOException {
+ Path link = new Path(testBaseDir1(), "linkToFile1");
+ wrapper.createSymlink(new Path("/doestNotExist"), link, false);
+ try {
+ wrapper.rename(link, link);
+ } catch (IOException e) {
+ assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
+ }
+ // Fails with overwrite as well
+ try {
+ wrapper.rename(link, link, Rename.OVERWRITE);
+ } catch (IOException e) {
+ assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Rename a symlink */
+ public void testRenameSymlink() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path link1 = new Path(testBaseDir1(), "linkToFile1");
+ Path link2 = new Path(testBaseDir1(), "linkToFile2");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, link1, false);
+ wrapper.rename(link1, link2);
+ assertTrue(wrapper.getFileLinkStatus(link2).isSymlink());
+ assertFalse(wrapper.getFileStatus(link2).isDirectory());
+ readFile(link2);
+ readFile(file);
+ try {
+ createAndWriteFile(link2);
+ fail("link was not renamed");
+ } catch (IOException x) {
+ // Expected
+ }
+ }
+
+ @Test(timeout=10000)
+ /** Rename a symlink to the file it links to */
+ public void testRenameSymlinkToFileItLinksTo() throws IOException {
+ /* NB: The rename is not atomic, so file is deleted before renaming
+ * linkToFile. In this interval linkToFile is dangling and local file
+ * system does not handle dangling links because File.exists returns
+ * false for dangling links. */
+ if ("file".equals(getScheme())) {
+ return;
+ }
+ Path file = new Path(testBaseDir1(), "file");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, link, false);
+ try {
+ wrapper.rename(link, file);
+ fail("Renamed symlink to its target");
+ } catch (IOException e) {
+ assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
+ }
+ // Check the rename didn't happen
+ assertTrue(wrapper.isFile(file));
+ assertTrue(wrapper.exists(link));
+ assertTrue(wrapper.isSymlink(link));
+ assertEquals(file, wrapper.getLinkTarget(link));
+ try {
+ wrapper.rename(link, file, Rename.OVERWRITE);
+ fail("Renamed symlink to its target");
+ } catch (IOException e) {
+ assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
+ }
+ // Check the rename didn't happen
+ assertTrue(wrapper.isFile(file));
+ assertTrue(wrapper.exists(link));
+ assertTrue(wrapper.isSymlink(link));
+ assertEquals(file, wrapper.getLinkTarget(link));
+ }
+
+ @Test(timeout=10000)
+ /** Rename a symlink to the directory it links to */
+ public void testRenameSymlinkToDirItLinksTo() throws IOException {
+ /* NB: The rename is not atomic, so dir is deleted before renaming
+ * linkToFile. In this interval linkToFile is dangling and local file
+ * system does not handle dangling links because File.exists returns
+ * false for dangling links. */
+ if ("file".equals(getScheme())) {
+ return;
+ }
+ Path dir = new Path(testBaseDir1(), "dir");
+ Path link = new Path(testBaseDir1(), "linkToDir");
+ wrapper.mkdir(dir, FileContext.DEFAULT_PERM, false);
+ wrapper.createSymlink(dir, link, false);
+ try {
+ wrapper.rename(link, dir);
+ fail("Renamed symlink to its target");
+ } catch (IOException e) {
+ assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
+ }
+ // Check the rename didn't happen
+ assertTrue(wrapper.isDir(dir));
+ assertTrue(wrapper.exists(link));
+ assertTrue(wrapper.isSymlink(link));
+ assertEquals(dir, wrapper.getLinkTarget(link));
+ try {
+ wrapper.rename(link, dir, Rename.OVERWRITE);
+ fail("Renamed symlink to its target");
+ } catch (IOException e) {
+ assertTrue(unwrapException(e) instanceof FileAlreadyExistsException);
+ }
+ // Check the rename didn't happen
+ assertTrue(wrapper.isDir(dir));
+ assertTrue(wrapper.exists(link));
+ assertTrue(wrapper.isSymlink(link));
+ assertEquals(dir, wrapper.getLinkTarget(link));
+ }
+
+ @Test(timeout=10000)
+ /** Test rename the symlink's target */
+ public void testRenameLinkTarget() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path fileNew = new Path(testBaseDir1(), "fileNew");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, link, false);
+ wrapper.rename(file, fileNew, Rename.OVERWRITE);
+ try {
+ readFile(link);
+ fail("Link should be dangling");
+ } catch (IOException x) {
+ // Expected
+ }
+ wrapper.rename(fileNew, file, Rename.OVERWRITE);
+ readFile(link);
+ }
+
+ @Test(timeout=10000)
+ /** Test rename a file to path with destination that has symlink parent */
+ public void testRenameFileWithDestParentSymlink() throws IOException {
+ Path link = new Path(testBaseDir1(), "link");
+ Path file1 = new Path(testBaseDir1(), "file1");
+ Path file2 = new Path(testBaseDir1(), "file2");
+ Path file3 = new Path(link, "file3");
+ Path dir2 = new Path(testBaseDir2());
+
+ // Renaming /dir1/file1 to non-existant file /dir1/link/file3 is OK
+ // if link points to a directory...
+ wrapper.createSymlink(dir2, link, false);
+ createAndWriteFile(file1);
+ wrapper.rename(file1, file3);
+ assertFalse(wrapper.exists(file1));
+ assertTrue(wrapper.exists(file3));
+ wrapper.rename(file3, file1);
+
+ // But fails if link is dangling...
+ wrapper.delete(link, false);
+ wrapper.createSymlink(file2, link, false);
+ try {
+ wrapper.rename(file1, file3);
+ } catch (IOException e) {
+ // Expected
+ assertTrue(unwrapException(e) instanceof FileNotFoundException);
+ }
+
+ // And if link points to a file...
+ createAndWriteFile(file2);
+ try {
+ wrapper.rename(file1, file3);
+ } catch (IOException e) {
+ // Expected
+ assertTrue(unwrapException(e) instanceof ParentNotDirectoryException);
+ }
+ }
+
+ @Test(timeout=10000)
+ /**
+ * 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 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");
+ wrapper.createSymlink(baseDir, linkToDir, false);
+ createAndWriteFile(fileViaLink);
+ assertTrue(wrapper.exists(fileViaLink));
+ assertTrue(wrapper.isFile(fileViaLink));
+ assertFalse(wrapper.isDir(fileViaLink));
+ assertFalse(wrapper.getFileLinkStatus(fileViaLink).isSymlink());
+ assertFalse(wrapper.isDir(fileViaLink));
+ assertEquals(wrapper.getFileStatus(file),
+ wrapper.getFileLinkStatus(file));
+ assertEquals(wrapper.getFileStatus(fileViaLink),
+ wrapper.getFileLinkStatus(fileViaLink));
+ readFile(fileViaLink);
+ appendToFile(fileViaLink);
+ wrapper.rename(fileViaLink, fileNewViaLink);
+ assertFalse(wrapper.exists(fileViaLink));
+ assertTrue(wrapper.exists(fileNewViaLink));
+ readFile(fileNewViaLink);
+ assertEquals(wrapper.getFileBlockLocations(fileNew, 0, 1).length,
+ wrapper.getFileBlockLocations(fileNewViaLink, 0, 1).length);
+ assertEquals(wrapper.getFileChecksum(fileNew),
+ wrapper.getFileChecksum(fileNewViaLink));
+ wrapper.delete(fileNewViaLink, true);
+ assertFalse(wrapper.exists(fileNewViaLink));
+ }
+
+ @Test(timeout=10000)
+ /**
+ * 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 linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path fileViaLink = new Path(linkToDir, "file");
+ wrapper.createSymlink(wrapper.makeQualified(baseDir), linkToDir, false);
+ createAndWriteFile(fileViaLink);
+ assertEquals(wrapper.getFileStatus(file),
+ wrapper.getFileLinkStatus(file));
+ assertEquals(wrapper.getFileStatus(fileViaLink),
+ wrapper.getFileLinkStatus(fileViaLink));
+ readFile(fileViaLink);
+ }
+
+ @Test(timeout=10000)
+ /**
+ * 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 dir = new Path(testBaseDir1(), "dir");
+ Path file = new Path(dir, "file");
+ Path linkToDir = new Path(testBaseDir1(), "linkToDir");
+ Path fileViaLink = new Path(linkToDir, "file");
+
+ wrapper.mkdir(dir, FileContext.DEFAULT_PERM, false);
+ wrapper.createSymlink(new Path("dir"), linkToDir, false);
+ createAndWriteFile(fileViaLink);
+ // Note that getFileStatus returns fully qualified paths even
+ // when called on an absolute path.
+ assertEquals(wrapper.makeQualified(file),
+ wrapper.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(wrapper.getFileStatus(file),
+ wrapper.getFileLinkStatus(file));
+ assertEquals(wrapper.getFileStatus(fileViaLink),
+ wrapper.getFileLinkStatus(fileViaLink));
+ assertEquals(wrapper.getFileStatus(fileViaLink),
+ wrapper.getFileLinkStatus(file));
+ }
+
+ @Test(timeout=10000)
+ /** Test create, list, and delete a directory through a symlink */
+ public void testAccessDirViaSymlink() throws IOException {
+ Path baseDir = new Path(testBaseDir1());
+ Path dir = new Path(testBaseDir1(), "dir");
+ Path linkToDir = new Path(testBaseDir2(), "linkToDir");
+ Path dirViaLink = new Path(linkToDir, "dir");
+ wrapper.createSymlink(baseDir, linkToDir, false);
+ wrapper.mkdir(dirViaLink, FileContext.DEFAULT_PERM, true);
+ assertTrue(wrapper.getFileStatus(dirViaLink).isDirectory());
+ FileStatus[] stats = wrapper.listStatus(dirViaLink);
+ assertEquals(0, stats.length);
+ RemoteIterator<FileStatus> statsItor = wrapper.listStatusIterator(dirViaLink);
+ assertFalse(statsItor.hasNext());
+ wrapper.delete(dirViaLink, false);
+ assertFalse(wrapper.exists(dirViaLink));
+ assertFalse(wrapper.exists(dir));
+ }
+
+ @Test(timeout=10000)
+ /** setTimes affects the target not the link */
+ public void testSetTimes() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file);
+ wrapper.createSymlink(file, link, false);
+ long at = wrapper.getFileLinkStatus(link).getAccessTime();
+ wrapper.setTimes(link, 2L, 3L);
+ // NB: local file systems don't implement setTimes
+ if (!"file".equals(getScheme())) {
+ assertEquals(at, wrapper.getFileLinkStatus(link).getAccessTime());
+ assertEquals(3, wrapper.getFileStatus(file).getAccessTime());
+ assertEquals(2, wrapper.getFileStatus(file).getModificationTime());
+ }
+ }
+}