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 sr...@apache.org on 2010/02/16 22:43:32 UTC
svn commit: r910706 [2/2] - in /hadoop/common/trunk: ./
src/java/org/apache/hadoop/fs/ src/java/org/apache/hadoop/fs/local/
src/java/org/apache/hadoop/util/ src/test/core/org/apache/hadoop/fs/
Added: hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/FileContextSymlinkBaseTest.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/FileContextSymlinkBaseTest.java?rev=910706&view=auto
==============================================================================
--- hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/FileContextSymlinkBaseTest.java (added)
+++ hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/FileContextSymlinkBaseTest.java Tue Feb 16 21:43:30 2010
@@ -0,0 +1,818 @@
+/**
+ * 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.Random;
+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.fs.FSDataInputStream;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.After;
+
+/**
+ * Test symbolic links using FileContext.
+ */
+public abstract class FileContextSymlinkBaseTest {
+ static final long seed = 0xDEADBEEFL;
+ static final int blockSize = 8192;
+ static final int fileSize = 16384;
+
+ protected static FileContext fc;
+
+ abstract protected String getScheme();
+ abstract protected String testBaseDir1();
+ abstract protected String testBaseDir2();
+ abstract protected URI testURI();
+
+ protected static void createAndWriteFile(FileContext fc, Path p)
+ throws IOException {
+ FSDataOutputStream out;
+ out = fc.create(p, EnumSet.of(CreateFlag.CREATE),
+ CreateOpts.createParent(),
+ CreateOpts.repFac((short) 1),
+ CreateOpts.blockSize(blockSize));
+ byte[] buf = new byte[fileSize];
+ Random rand = new Random(seed);
+ rand.nextBytes(buf);
+ out.write(buf);
+ out.close();
+ }
+
+ protected static void createAndWriteFile(Path p) throws IOException {
+ createAndWriteFile(fc, p);
+ }
+
+ protected void readFile(Path p) throws IOException {
+ FSDataInputStream out = fc.open(p);
+ byte[] actual = new byte[fileSize];
+ out.readFully(actual);
+ out.close();
+ }
+
+ protected void readFile(FileContext fc, Path p) throws IOException {
+ FSDataInputStream out = fc.open(p);
+ byte[] actual = new byte[fileSize];
+ out.readFully(actual);
+ out.close();
+ }
+
+ protected void appendToFile(Path p) throws IOException {
+ FSDataOutputStream out;
+ out = fc.create(p, EnumSet.of(CreateFlag.APPEND));
+ byte[] buf = new byte[fileSize];
+ Random rand = new Random(seed);
+ rand.nextBytes(buf);
+ out.write(buf);
+ out.close();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ fc.mkdir(new Path(testBaseDir1()), FileContext.DEFAULT_PERM, true);
+ fc.mkdir(new Path(testBaseDir2()), FileContext.DEFAULT_PERM, true);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ fc.delete(new Path(testBaseDir1()), true);
+ fc.delete(new Path(testBaseDir2()), true);
+ }
+
+ @Test
+ /** The root is not a symlink */
+ public void testStatRoot() throws IOException {
+ assertFalse(fc.getFileLinkStatus(new Path("/")).isSymlink());
+ }
+
+ @Test
+ /** Test setWorkingDirectory resolves symlinks */
+ public void testSetWDResolvesLinks() throws IOException {
+ Path dir = new Path(testBaseDir1());
+ Path linkToDir = new Path(testBaseDir1()+"/link");
+ fc.createSymlink(dir, linkToDir, false);
+ fc.setWorkingDirectory(linkToDir);
+ // Local file system does not resolve symlinks, others do.
+ if ("file".equals(getScheme())) {
+ assertEquals(linkToDir.getName(), fc.getWorkingDirectory().getName());
+ } else {
+ assertEquals(dir.getName(), fc.getWorkingDirectory().getName());
+ }
+ }
+
+ @Test
+ /** Test create a dangling link */
+ public void testCreateDanglingLink() throws IOException {
+ Path file = new Path("/noSuchFile");
+ Path link = new Path(testBaseDir1()+"/link");
+ try {
+ fc.createSymlink(file, link, false);
+ } catch (IOException x) {
+ fail("failed to create dangling symlink");
+ }
+ try {
+ fc.getFileStatus(link);
+ fail("Got file status of non-existant file");
+ } catch (FileNotFoundException f) {
+ // Expected
+ }
+ fc.delete(link, false);
+ }
+
+ @Test
+ /** Test create a link to null and empty path */
+ public void testCreateLinkToNullEmpty() throws IOException {
+ Path link = new Path(testBaseDir1()+"/link");
+ try {
+ fc.createSymlink(null, link, false);
+ fail("Can't create symlink to null");
+ } catch (java.lang.NullPointerException e) {
+ // Expected, create* with null yields NPEs
+ }
+ try {
+ fc.createSymlink(new Path(""), link, false);
+ fail("Can't create symlink to empty string");
+ } catch (java.lang.IllegalArgumentException e) {
+ // Expected, Path("") is invalid
+ }
+ }
+
+ @Test
+ /** 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);
+ fc.delete(new Path(testBaseDir2()), true);
+ try {
+ fc.createSymlink(file, link, false);
+ fail("Created link without first creating parent dir");
+ } catch (IOException x) {
+ // Expected. Need to create testBaseDir2() first.
+ }
+ assertFalse(fc.exists(new Path(testBaseDir2())));
+ fc.createSymlink(file, link, true);
+ readFile(link);
+ }
+
+ @Test
+ /** Delete a link */
+ public void testDeleteLink() throws IOException {
+ Path file = new Path(testBaseDir1()+"/file");
+ Path link = new Path(testBaseDir1()+"/linkToFile");
+ createAndWriteFile(file);
+ fc.createSymlink(file, link, false);
+ readFile(link);
+ fc.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
+ fc.createSymlink(file, link, false);
+ }
+
+ @Test
+ /** Ensure open resolves symlinks */
+ public void testOpenResolvesLinks() throws IOException {
+ Path file = new Path(testBaseDir1()+"/noSuchFile");
+ Path link = new Path(testBaseDir1()+"/link");
+ fc.createSymlink(file, link, false);
+ try {
+ fc.open(link);
+ fail("link target does not exist");
+ } catch (FileNotFoundException x) {
+ // Expected
+ }
+ fc.delete(link, false);
+ }
+
+ @Test
+ /** Stat a link to a file */
+ public void testStatLinkToFile() throws IOException {
+ Path file = new Path(testBaseDir1()+"/file");
+ Path link = new Path(testBaseDir1()+"/linkToFile");
+ createAndWriteFile(file);
+ readFile(file);
+ fc.createSymlink(file, link, false);
+ assertFalse(fc.getFileStatus(link).isSymlink());
+ assertFalse(fc.getFileStatus(link).isDir());
+ assertTrue(fc.getFileLinkStatus(link).isSymlink());
+ assertFalse(fc.getFileLinkStatus(link).isDir());
+ assertTrue(fc.isFile(link));
+ assertFalse(fc.isDirectory(link));
+ assertEquals(file.toUri().getPath(), fc.getLinkTarget(link).toString());
+ }
+
+ @Test
+ /** Stat a link to a directory */
+ public void testStatLinkToDir() throws IOException {
+ Path dir = new Path(testBaseDir1());
+ Path link = new Path(testBaseDir1()+"/linkToDir");
+ fc.createSymlink(dir, link, false);
+ assertFalse(fc.getFileStatus(link).isSymlink());
+ assertTrue(fc.getFileStatus(link).isDir());
+ assertTrue(fc.getFileLinkStatus(link).isSymlink());
+ assertFalse(fc.getFileLinkStatus(link).isDir());
+ assertFalse(fc.isFile(link));
+ assertTrue(fc.isDirectory(link));
+ assertEquals(dir.toUri().getPath(), fc.getLinkTarget(link).toString());
+ }
+
+ @Test
+ /** lstat a non-existant file */
+ public void testStatNonExistantFiles() throws IOException {
+ Path fileAbs = new Path("/doesNotExist");
+ try {
+ fc.getFileLinkStatus(fileAbs);
+ fail("Got FileStatus for non-existant file");
+ } catch (FileNotFoundException f) {
+ // Expected
+ }
+ try {
+ fc.getLinkTarget(fileAbs);
+ fail("Got link target for non-existant file");
+ } catch (FileNotFoundException f) {
+ // Expected
+ }
+ }
+
+ @Test
+ /** 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 {
+ fc.getLinkTarget(dir);
+ fail("Lstat'd a non-symlink");
+ } catch (IOException e) {
+ // Expected.
+ }
+ try {
+ fc.getLinkTarget(file);
+ fail("Lstat'd a non-symlink");
+ } catch (IOException e) {
+ // Expected.
+ }
+ }
+
+ @Test
+ /** Test links that link to each other */
+ public void testRecursiveLinks() throws IOException {
+ Path link1 = new Path(testBaseDir1()+"/link1");
+ Path link2 = new Path(testBaseDir1()+"/link2");
+ fc.createSymlink(link1, link2, false);
+ fc.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());
+ }
+ }
+
+ private void checkLink(Path linkAbs, Path expectedTarget, Path targetQual)
+ throws IOException {
+ Path dir = new Path(testBaseDir1());
+ // isFile/Directory
+ assertTrue(fc.isFile(linkAbs));
+ assertFalse(fc.isDirectory(linkAbs));
+
+ // Check getFileStatus
+ assertFalse(fc.getFileStatus(linkAbs).isSymlink());
+ assertFalse(fc.getFileStatus(linkAbs).isDir());
+ assertEquals(fileSize, fc.getFileStatus(linkAbs).getLen());
+
+ // Check getFileLinkStatus
+ assertTrue(fc.getFileLinkStatus(linkAbs).isSymlink());
+ assertFalse(fc.getFileLinkStatus(linkAbs).isDir());
+
+ // Check getSymlink always returns a qualified target, except
+ // when partially qualified paths are used (see tests below).
+ assertEquals(targetQual.toString(),
+ fc.getFileLinkStatus(linkAbs).getSymlink().toString());
+ assertEquals(targetQual, fc.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, fc.getLinkTarget(linkAbs));
+
+ // Now read using all path types..
+ fc.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 = "file".equals(getScheme()) ? false : true;
+ try {
+ readFile(new Path(getScheme()+"://"+testBaseDir1()+"/linkToFile"));
+ assertFalse(failureExpected);
+ } catch (Exception e) {
+ assertTrue(failureExpected);
+ }
+
+ // Now read using a different file context (for HDFS at least)
+ if (!"file".equals(getScheme())) {
+ FileContext localFc = FileContext.getLocalFSFileContext();
+ readFile(localFc, new Path(testURI().toString(), linkAbs));
+ }
+ }
+
+ @Test
+ /** 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);
+
+ fc.setWorkingDirectory(new Path(testBaseDir1()));
+ fc.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");
+ fc.rename(dir1, dir2, Rename.OVERWRITE);
+ assertEquals(fileViaDir2, fc.getFileLinkStatus(linkViaDir2).getSymlink());
+ readFile(linkViaDir2);
+ }
+
+ @Test
+ /** 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);
+
+ fc.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");
+ fc.rename(dir1, dir2, Rename.OVERWRITE);
+ assertEquals(fileQual, fc.getFileLinkStatus(linkViaDir2).getSymlink());
+ try {
+ readFile(linkViaDir2);
+ fail("The target should not exist");
+ } catch (FileNotFoundException x) {
+ // Expected
+ }
+ }
+
+ @Test
+ /**
+ * 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);
+
+ fc.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");
+ fc.rename(dir1, dir2, Rename.OVERWRITE);
+ assertEquals(fileQual, fc.getFileLinkStatus(linkViaDir2).getSymlink());
+ try {
+ readFile(linkViaDir2);
+ fail("The target should not exist");
+ } catch (FileNotFoundException x) {
+ // Expected
+ }
+ }
+
+ @Test
+ /**
+ * 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 {
+ 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");
+
+ // Partially qualified paths are covered for local file systems
+ // in the previous test.
+ if ("file".equals(getScheme())) {
+ return;
+ }
+ FileContext localFc = FileContext.getLocalFSFileContext();
+
+ fc.createSymlink(fileWoHost, link, false);
+ // Partially qualified path is stored
+ assertEquals(fileWoHost, fc.getLinkTarget(linkQual));
+ // NB: We do not add an authority
+ assertEquals(fileWoHost.toString(),
+ fc.getFileLinkStatus(link).getSymlink().toString());
+ assertEquals(fileWoHost.toString(),
+ fc.getFileLinkStatus(linkQual).getSymlink().toString());
+ // Ditto even from another file system
+ assertEquals(fileWoHost.toString(),
+ localFc.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) {
+ // Expected
+ }
+ }
+
+ @Test
+ /** 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;
+ }
+ fc.createSymlink(fileWoScheme, link, false);
+ assertEquals(fileWoScheme, fc.getLinkTarget(link));
+ assertEquals(fileWoScheme.toString(),
+ fc.getFileLinkStatus(link).getSymlink().toString());
+ try {
+ readFile(link);
+ fail("Accessed a file with w/o scheme");
+ } catch (IOException e) {
+ // Expected
+ assertEquals("No AbstractFileSystem for scheme: null", e.getMessage());
+ }
+ }
+
+ @Test
+ /** 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(fc.getFileStatus(file), fc.getFileLinkStatus(file));
+ assertEquals(fc.getFileStatus(dir), fc.getFileLinkStatus(dir));
+ try {
+ fc.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 {
+ fc.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
+ /** 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);
+ fc.createSymlink(dir1, linkToDir, false);
+ assertFalse(fc.isFile(linkToDir));
+ assertTrue(fc.isDirectory(linkToDir));
+ assertTrue(fc.getFileStatus(linkToDir).isDir());
+ assertTrue(fc.getFileLinkStatus(linkToDir).isSymlink());
+ }
+
+ @Test
+ /** 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");
+ fc.createSymlink(dir, linkToDir, false);
+ createAndWriteFile(fileViaLink);
+ assertTrue(fc.isFile(fileViaLink));
+ assertFalse(fc.isDirectory(fileViaLink));
+ assertFalse(fc.getFileLinkStatus(fileViaLink).isSymlink());
+ assertFalse(fc.getFileStatus(fileViaLink).isDir());
+ readFile(fileViaLink);
+ fc.delete(fileViaLink, true);
+ assertFalse(fc.exists(fileViaLink));
+ }
+
+ @Test
+ /** 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");
+ fc.createSymlink(dir1, linkToDir, false);
+ fc.mkdir(subDirViaLink, FileContext.DEFAULT_PERM, true);
+ assertTrue(fc.getFileStatus(subDirViaLink).isDir());
+ fc.delete(subDirViaLink, false);
+ assertFalse(fc.exists(subDirViaLink));
+ assertFalse(fc.exists(subDir));
+ }
+
+ @Test
+ /** 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);
+ fc.createSymlink(dir1, linkToDir, false);
+ fc.createSymlink(fileViaLink, linkToFile, false);
+ assertTrue(fc.isFile(linkToFile));
+ assertTrue(fc.getFileLinkStatus(linkToFile).isSymlink());
+ readFile(linkToFile);
+ assertEquals(fileSize, fc.getFileStatus(linkToFile).getLen());
+ assertEquals(fileViaLink, fc.getLinkTarget(linkToFile));
+ }
+
+ @Test
+ /** 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);
+ fc.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).
+ assertTrue(fc.listStatus(link).length == 2 ||
+ fc.listStatus(link).length == 3);
+ }
+
+ @Test
+ /** 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);
+ fc.createSymlink(file, link, false);
+ try {
+ fc.createSymlink(file, link, false);
+ fail("link already exists");
+ } catch (IOException x) {
+ // Expected
+ }
+ }
+
+ @Test
+ /** 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);
+ fc.createSymlink(dir1, linkToDir, false);
+ fc.createSymlink(linkToDir, linkToLink, false);
+ assertTrue(fc.isFile(fileViaLink));
+ assertFalse(fc.isDirectory(fileViaLink));
+ assertFalse(fc.getFileLinkStatus(fileViaLink).isSymlink());
+ assertFalse(fc.getFileStatus(fileViaLink).isDir());
+ readFile(fileViaLink);
+ }
+
+ @Test
+ /** 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);
+ fc.createSymlink(file, link, false);
+ try {
+ createAndWriteFile(link);
+ fail("link already exists");
+ } catch (IOException x) {
+ // Expected
+ }
+ try {
+ fc.mkdir(link, FsPermission.getDefault(), false);
+ fail("link already exists");
+ } catch (IOException x) {
+ // Expected
+ }
+ }
+
+ @Test
+ /** 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);
+ fc.createSymlink(file, link, false);
+ fc.delete(link, false);
+ try {
+ readFile(link);
+ fail("link was deleted");
+ } catch (IOException x) {
+ // Expected
+ }
+ readFile(file);
+ fc.createSymlink(file, link, false);
+ readFile(link);
+ }
+
+
+ @Test
+ /** 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);
+ fc.setWorkingDirectory(dir);
+ try {
+ fc.createSymlink(new Path("."), link, false);
+ fail("Created symlink to dot");
+ readFile(new Path(testBaseDir1(), "linkToDot/file"));
+ } catch (IOException x) {
+ // Expected. Path(".") resolves to "" because URI normalizes
+ // the dot away and AbstractFileSystem considers "" invalid.
+ }
+ }
+
+ @Test
+ /** 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);
+ fc.createSymlink(dotDot, linkToDir, false);
+ readFile(fileViaLink);
+ assertEquals(fileSize, fc.getFileStatus(fileViaLink).getLen());
+ }
+
+ @Test
+ /** Test create symlink to ../foo */
+ 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);
+ fc.mkdir(dir, FsPermission.getDefault(), false);
+ fc.setWorkingDirectory(dir);
+ fc.createSymlink(new Path("../file"), link, false);
+ readFile(link);
+ assertEquals(new Path("../file"), fc.getLinkTarget(link));
+ }
+
+ @Test
+ /** Append data to a file specified using a symlink */
+ public void testAppendFileViaSymlink() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file);
+ fc.createSymlink(file, link, false);
+ assertEquals(fileSize, fc.getFileStatus(link).getLen());
+ appendToFile(link);
+ assertEquals(fileSize*2, fc.getFileStatus(link).getLen());
+ }
+
+ @Test
+ /** Test rename file through a symlink */
+ public void testRenameFileViaSymlink() 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 fileNewViaLink = new Path(linkToDir, "fileNew");
+ createAndWriteFile(file);
+ fc.createSymlink(dir1, linkToDir, false);
+ fc.rename(fileViaLink, fileNewViaLink, Rename.OVERWRITE);
+ assertFalse(fc.exists(fileViaLink));
+ assertFalse(fc.exists(file));
+ assertTrue(fc.exists(fileNewViaLink));
+ }
+
+ @Test
+ /** 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);
+ fc.createSymlink(file, link1, false);
+ fc.rename(link1, link2);
+ assertTrue(fc.getFileLinkStatus(link2).isSymlink());
+ assertFalse(fc.getFileStatus(link2).isDir());
+ readFile(link2);
+ readFile(file);
+ try {
+ createAndWriteFile(link2);
+ fail("link was not renamed");
+ } catch (IOException x) {
+ // Expected
+ }
+ }
+
+ @Test
+ /** Test renaming symlink target */
+ public void testMoveLinkTarget() throws IOException {
+ Path file = new Path(testBaseDir1(), "file");
+ Path fileNew = new Path(testBaseDir1(), "fileNew");
+ Path link = new Path(testBaseDir1(), "linkToFile");
+ createAndWriteFile(file);
+ fc.createSymlink(file, link, false);
+ fc.rename(file, fileNew, Rename.OVERWRITE);
+ try {
+ readFile(link);
+ fail("link target was renamed");
+ } catch (IOException x) {
+ // Expected
+ }
+ fc.rename(fileNew, file, Rename.OVERWRITE);
+ readFile(link);
+ }
+
+ @Test
+ /** 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);
+ fc.createSymlink(file, link, false);
+ long at = fc.getFileLinkStatus(link).getAccessTime();
+ fc.setTimes(link, 2L, 3L);
+ // NB: local file systems don't implement setTimes
+ if (!"file".equals(getScheme())) {
+ assertEquals(at, fc.getFileLinkStatus(link).getAccessTime());
+ assertEquals(3, fc.getFileStatus(file).getAccessTime());
+ assertEquals(2, fc.getFileStatus(file).getModificationTime());
+ }
+ }
+}
Added: hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/TestLocalFSFileContextSymlink.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/TestLocalFSFileContextSymlink.java?rev=910706&view=auto
==============================================================================
--- hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/TestLocalFSFileContextSymlink.java (added)
+++ hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/TestLocalFSFileContextSymlink.java Tue Feb 16 21:43:30 2010
@@ -0,0 +1,179 @@
+/**
+ * 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.net.URISyntaxException;
+
+import org.apache.hadoop.fs.FileContext;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.FileUtil;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.Before;
+
+/**
+ * Test symbolic links using FileContext and LocalFs.
+ */
+public class TestLocalFSFileContextSymlink extends FileContextSymlinkBaseTest {
+
+ protected String getScheme() {
+ return "file";
+ }
+
+ protected String testBaseDir1() {
+ return "/tmp/test1";
+ }
+
+ protected String testBaseDir2() {
+ return "/tmp/test2";
+ }
+
+ protected URI testURI() {
+ try {
+ return new URI("file:///");
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ fc = FileContext.getLocalFSFileContext();
+ super.setUp();
+ }
+
+ @Test
+ /** Test access a symlink using FileSystem */
+ public void testAccessLinkFromFileSystem() throws IOException {
+ Path fileAbs = new Path(testBaseDir1()+"/file");
+ Path link = new Path(testBaseDir1()+"/linkToFile");
+ createAndWriteFile(fileAbs);
+ fc.createSymlink(fileAbs, link, false);
+ readFile(link);
+ }
+
+ @Test
+ /** lstat a non-existant file using a partially qualified path */
+ public void testDanglingLinkFilePartQual() throws IOException {
+ Path filePartQual = new Path(getScheme()+":///doesNotExist");
+ try {
+ fc.getFileLinkStatus(filePartQual);
+ fail("Got FileStatus for non-existant file");
+ } catch (FileNotFoundException f) {
+ // Expected
+ }
+ try {
+ fc.getLinkTarget(filePartQual);
+ fail("Got link target for non-existant file");
+ } catch (FileNotFoundException f) {
+ // Expected
+ }
+ }
+
+ @Test
+ /** Stat and lstat a dangling link */
+ public void testDanglingLink() throws IOException {
+ Path fileAbs = new Path(testBaseDir1()+"/file");
+ Path fileQual = new Path(testURI().toString(), fileAbs);
+ Path link = new Path(testBaseDir1()+"/linkToFile");
+ fc.createSymlink(fileAbs, link, false);
+ // Deleting the link using FileContext currently fails because
+ // resolve looks up LocalFs rather than RawLocalFs for the path
+ // so we call ChecksumFs delete (which doesn't delete dangling
+ // links) instead of delegating to delete in RawLocalFileSystem
+ // which deletes via fullyDelete. testDeleteLink above works
+ // because the link is not dangling.
+ //assertTrue(fc.delete(link, false));
+ FileUtil.fullyDelete(new File(link.toUri().getPath()));
+ fc.createSymlink(fileAbs, link, false);
+ try {
+ fc.getFileStatus(link);
+ fail("Got FileStatus for dangling link");
+ } catch (FileNotFoundException f) {
+ // Expected. File's exists method returns false for dangling links
+ }
+ // We can stat a dangling link
+ FileStatus fsd = fc.getFileLinkStatus(link);
+ assertEquals(fileQual, fsd.getSymlink());
+ assertTrue(fsd.isSymlink());
+ assertFalse(fsd.isDir());
+ assertEquals("", fsd.getOwner());
+ assertEquals("", fsd.getGroup());
+ assertEquals(link, fsd.getPath());
+ assertEquals(0, fsd.getLen());
+ assertEquals(0, fsd.getBlockSize());
+ assertEquals(0, fsd.getReplication());
+ assertEquals(0, fsd.getAccessTime());
+ assertEquals(FsPermission.getDefault(), fsd.getPermission());
+ // Accessing the link
+ try {
+ readFile(link);
+ fail("Got FileStatus for dangling link");
+ } catch (FileNotFoundException f) {
+ // Ditto.
+ }
+ // Creating the file makes the link work
+ createAndWriteFile(fileAbs);
+ fc.getFileStatus(link);
+ }
+
+ @Test
+ /**
+ * Test getLinkTarget with a partially qualified target.
+ * NB: Hadoop does not support fully qualified URIs for the
+ * file scheme (eg file://host/tmp/test).
+ */
+ public void testGetLinkStatusPartQualTarget() throws IOException {
+ Path fileAbs = new Path(testBaseDir1()+"/file");
+ Path fileQual = new Path(testURI().toString(), fileAbs);
+ Path dir = new Path(testBaseDir1());
+ Path link = new Path(testBaseDir1()+"/linkToFile");
+ Path dirNew = new Path(testBaseDir2());
+ Path linkNew = new Path(testBaseDir2()+"/linkToFile");
+ fc.delete(dirNew, true);
+ createAndWriteFile(fileQual);
+ fc.setWorkingDirectory(dir);
+ // Link target is partially qualified, we get the same back.
+ fc.createSymlink(fileQual, link, false);
+ assertEquals(fileQual, fc.getFileLinkStatus(link).getSymlink());
+ // Because the target was specified with an absolute path the
+ // link fails to resolve after moving the parent directory.
+ fc.rename(dir, dirNew);
+ // The target is still the old path
+ assertEquals(fileQual, fc.getFileLinkStatus(linkNew).getSymlink());
+ try {
+ readFile(linkNew);
+ fail("The link should be dangling now.");
+ } catch (FileNotFoundException x) {
+ // Expected.
+ }
+ // RawLocalFs only maintains the path part, not the URI, and
+ // therefore does not support links to other file systems.
+ Path anotherFs = new Path("hdfs://host:1000/dir/file");
+ FileUtil.fullyDelete(new File("/tmp/test2/linkToFile"));
+ try {
+ fc.createSymlink(anotherFs, linkNew, false);
+ fail("Created a local fs link to a non-local fs");
+ } catch (IOException x) {
+ // Excpected.
+ }
+ }
+}
Modified: hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/TestPath.java
URL: http://svn.apache.org/viewvc/hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/TestPath.java?rev=910706&r1=910705&r2=910706&view=diff
==============================================================================
--- hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/TestPath.java (original)
+++ hadoop/common/trunk/src/test/core/org/apache/hadoop/fs/TestPath.java Tue Feb 16 21:43:30 2010
@@ -61,6 +61,10 @@
public void testNormalize() {
assertEquals("/", new Path("//").toString());
+ assertEquals("/", new Path("///").toString());
+ assertEquals("//foo/", new Path("//foo/").toString());
+ assertEquals("//foo/", new Path("//foo//").toString());
+ assertEquals("//foo/bar", new Path("//foo//bar").toString());
assertEquals("/foo", new Path("/foo/").toString());
assertEquals("/foo", new Path("/foo/").toString());
assertEquals("foo", new Path("foo/").toString());
@@ -176,6 +180,19 @@
// if the child uri is absolute path
assertEquals("foo://bar/fud#boo", new Path(new Path(new URI(
"foo://bar/baz#bud")), new Path(new URI("/fud#boo"))).toString());
+ }
+
+ public void testMakeQualified() throws URISyntaxException {
+ URI defaultUri = new URI("hdfs://host1/dir1");
+ URI wd = new URI("hdfs://host2/dir2");
+
+ // The scheme from defaultUri is used but the path part is not
+ assertEquals(new Path("hdfs://host1/dir/file"),
+ new Path("file").makeQualified(defaultUri, new Path("/dir")));
+
+ // The defaultUri is only used if the path + wd has no scheme
+ assertEquals(new Path("hdfs://host2/dir2/file"),
+ new Path("file").makeQualified(defaultUri, new Path(wd)));
}
}