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)));
  }
 
 }