瀏覽代碼

HDFS-6314. Test cases for XAttrs. Contributed by Yi Liu.

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/HDFS-2006@1593547 13f79535-47bb-0310-9956-ffa450edef68
Uma Maheswara Rao G 11 年之前
父節點
當前提交
4bd55d2abf

+ 2 - 0
hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-2006.txt

@@ -25,6 +25,8 @@ HDFS-2006 (Unreleased)
 
     HDFS-6298. XML based End-to-End test for getfattr and setfattr commands. (Yi Liu via umamahesh)
 
+    HDFS-6314. Test cases for XAttrs. (Yi Liu via umamahesh)
+
   OPTIMIZATIONS
 
   BUG FIXES

+ 85 - 0
hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/fs/TestXAttr.java

@@ -0,0 +1,85 @@
+/**
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests for <code>XAttr</code> objects.
+ */
+public class TestXAttr {
+  private static XAttr XATTR, XATTR1, XATTR2, XATTR3, XATTR4;
+  
+  @BeforeClass
+  public static void setUp() throws Exception {
+    byte[] value = {0x31, 0x32, 0x33};
+    XATTR = new XAttr.Builder()
+      .setName("name")
+      .setValue(value)
+      .build();
+    XATTR1 = new XAttr.Builder()
+      .setNameSpace(XAttr.NameSpace.USER)
+      .setName("name")
+      .setValue(value)
+      .build();
+    XATTR2 = new XAttr.Builder()
+      .setNameSpace(XAttr.NameSpace.TRUSTED)
+      .setName("name")
+      .setValue(value)
+      .build();
+    XATTR3 = new XAttr.Builder()
+      .setNameSpace(XAttr.NameSpace.SYSTEM)
+      .setName("name")
+      .setValue(value)
+      .build();
+    XATTR4 = new XAttr.Builder()
+      .setNameSpace(XAttr.NameSpace.SECURITY)
+      .setName("name")
+      .setValue(value)
+      .build();
+  }
+  
+  @Test
+  public void testXAttrEquals() {
+    assertNotSame(XATTR1, XATTR2);
+    assertNotSame(XATTR2, XATTR3);
+    assertNotSame(XATTR3, XATTR4);
+    assertEquals(XATTR, XATTR1);
+    assertEquals(XATTR1, XATTR1);
+    assertEquals(XATTR2, XATTR2);
+    assertEquals(XATTR3, XATTR3);
+    assertEquals(XATTR4, XATTR4);
+    assertFalse(XATTR1.equals(XATTR2));
+    assertFalse(XATTR2.equals(XATTR3));
+    assertFalse(XATTR3.equals(XATTR4));
+  }
+  
+  @Test
+  public void testXAttrHashCode() {
+    assertEquals(XATTR.hashCode(), XATTR1.hashCode());
+    assertFalse(XATTR1.hashCode() == XATTR2.hashCode());
+    assertFalse(XATTR2.hashCode() == XATTR3.hashCode());
+    assertFalse(XATTR3.hashCode() == XATTR4.hashCode());
+  }
+}

+ 75 - 0
hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSShell.java

@@ -1874,6 +1874,81 @@ public class TestDFSShell {
       cluster.shutdown();
     }
   }
+  
+  @Test (timeout = 30000)
+  public void testSetXAttrPermission() throws Exception {
+    UserGroupInformation user = UserGroupInformation.
+        createUserForTesting("user", new String[] {"mygroup"});
+    MiniDFSCluster cluster = null;
+    PrintStream bak = null;
+    try {
+      final Configuration conf = new HdfsConfiguration();
+      cluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).build();
+      cluster.waitActive();
+      
+      FileSystem fs = cluster.getFileSystem();
+      Path p = new Path("/foo");
+      fs.mkdirs(p);
+      bak = System.err;
+      
+      final FsShell fshell = new FsShell(conf);
+      final ByteArrayOutputStream out = new ByteArrayOutputStream();
+      System.setErr(new PrintStream(out));
+      
+      // No permission to write xattr
+      fs.setPermission(p, new FsPermission((short) 0700));
+      user.doAs(new PrivilegedExceptionAction<Object>() {
+        @Override
+        public Object run() throws Exception {
+          int ret = ToolRunner.run(fshell, new String[]{
+              "-setfattr", "-n", "user.a1", "-v", "1234", "/foo"});
+          assertEquals("Returned should be 1", 1, ret);
+          String str = out.toString();
+          assertTrue("Permission denied printed", 
+              str.indexOf("Permission denied") != -1);
+          out.reset();
+          return null;
+        }
+      });
+      
+      int ret = ToolRunner.run(fshell, new String[]{
+          "-setfattr", "-n", "user.a1", "-v", "1234", "/foo"});
+      assertEquals("Returned should be 0", 0, ret);
+      out.reset();
+      
+      // No permission to read and remove
+      fs.setPermission(p, new FsPermission((short) 0750));
+      user.doAs(new PrivilegedExceptionAction<Object>() {
+        @Override
+        public Object run() throws Exception {
+          // Read
+          int ret = ToolRunner.run(fshell, new String[]{
+              "-getfattr", "-n", "user.a1", "/foo"});
+          assertEquals("Returned should be 1", 1, ret);
+          String str = out.toString();
+          assertTrue("Permission denied printed",
+              str.indexOf("Permission denied") != -1);
+          out.reset();           
+          // Remove
+          ret = ToolRunner.run(fshell, new String[]{
+              "-setfattr", "-x", "user.a1", "/foo"});
+          assertEquals("Returned should be 1", 1, ret);
+          str = out.toString();
+          assertTrue("Permission denied printed",
+              str.indexOf("Permission denied") != -1);
+          out.reset();  
+          return null;
+        }
+      });
+    } finally {
+      if (bak != null) {
+        System.setErr(bak);
+      }
+      if (cluster != null) {
+        cluster.shutdown();
+      }
+    }
+  }
 
   /**
    * Test that the server trash configuration is respected when

+ 124 - 21
hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/FSXAttrBaseTest.java

@@ -40,6 +40,7 @@ import com.google.common.collect.Lists;
 
 /**
  * Tests NameNode interaction for all XAttr APIs.
+ * This test suite covers restarting the NN, saving a new checkpoint. 
  */
 public class FSXAttrBaseTest {
   
@@ -48,7 +49,7 @@ public class FSXAttrBaseTest {
   private static int pathCount = 0;
   private static Path path;
   
-  //xattrs
+  // XAttrs
   protected static final String name1 = "user.a1";
   protected static final byte[] value1 = {0x31, 0x32, 0x33};
   protected static final byte[] newValue1 = {0x31, 0x31, 0x31};
@@ -81,9 +82,10 @@ public class FSXAttrBaseTest {
   
   /**
    * Tests for creating xattr
-   * 1. create xattr using XAttrSetFlag.CREATE flag.
-   * 2. Assert exception of creating xattr which already exists.
-   * 3. Create multiple xattrs
+   * 1. Create an xattr using XAttrSetFlag.CREATE.
+   * 2. Create an xattr which already exists and expect an exception.
+   * 3. Create multiple xattrs.
+   * 4. Restart NN and save checkpoint scenarios.
    */
   @Test
   public void testCreateXAttr() throws Exception {
@@ -99,7 +101,7 @@ public class FSXAttrBaseTest {
     xattrs = fs.getXAttrs(path);
     Assert.assertEquals(xattrs.size(), 0);
     
-    //create xattr which already exists.
+    // Create xattr which already exists.
     fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
     try {
       fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
@@ -108,7 +110,7 @@ public class FSXAttrBaseTest {
     }
     fs.removeXAttr(path, name1);
     
-    //create two xattrs
+    // Create two xattrs
     fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
     fs.setXAttr(path, name2, null, EnumSet.of(XAttrSetFlag.CREATE));
     xattrs = fs.getXAttrs(path);
@@ -116,15 +118,30 @@ public class FSXAttrBaseTest {
     Assert.assertArrayEquals(value1, xattrs.get(name1));
     Assert.assertArrayEquals(new byte[0], xattrs.get(name2));
     
+    restart(false);
+    initFileSystem();
+    xattrs = fs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(new byte[0], xattrs.get(name2));
+    
+    restart(true);
+    initFileSystem();
+    xattrs = fs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(new byte[0], xattrs.get(name2));
+    
     fs.removeXAttr(path, name1);
     fs.removeXAttr(path, name2);
   }
   
   /**
    * Tests for replacing xattr
-   * 1. Replace xattr using XAttrSetFlag.REPLACE flag.
-   * 2. Assert exception of replacing xattr which does not exist.
-   * 3. Create multiple xattrs, and replace some.
+   * 1. Replace an xattr using XAttrSetFlag.REPLACE.
+   * 2. Replace an xattr which doesn't exist and expect an exception.
+   * 3. Create multiple xattrs and replace some.
+   * 4. Restart NN and save checkpoint scenarios.
    */
   @Test
   public void testReplaceXAttr() throws Exception {
@@ -138,14 +155,14 @@ public class FSXAttrBaseTest {
     
     fs.removeXAttr(path, name1);
     
-    //replace xattr which does not exist.
+    // Replace xattr which does not exist.
     try {
       fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.REPLACE));
       Assert.fail("Replacing xattr which does not exist should fail.");
     } catch (IOException e) {
     }
     
-    //create two xattrs, then replace one
+    // Create two xattrs, then replace one
     fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
     fs.setXAttr(path, name2, value2, EnumSet.of(XAttrSetFlag.CREATE));
     fs.setXAttr(path, name2, null, EnumSet.of(XAttrSetFlag.REPLACE));
@@ -154,6 +171,20 @@ public class FSXAttrBaseTest {
     Assert.assertArrayEquals(value1, xattrs.get(name1));
     Assert.assertArrayEquals(new byte[0], xattrs.get(name2));
     
+    restart(false);
+    initFileSystem();
+    xattrs = fs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(new byte[0], xattrs.get(name2));
+    
+    restart(true);
+    initFileSystem();
+    xattrs = fs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(new byte[0], xattrs.get(name2));
+    
     fs.removeXAttr(path, name1);
     fs.removeXAttr(path, name2);
   }
@@ -161,9 +192,9 @@ public class FSXAttrBaseTest {
   /**
    * Tests for setting xattr
    * 1. Set xattr with XAttrSetFlag.CREATE|XAttrSetFlag.REPLACE flag.
-   * 2. Set xattr with illegal name
+   * 2. Set xattr with illegal name.
    * 3. Set xattr without XAttrSetFlag.
-   * 4. Set xattr and total number exceeds max limit
+   * 4. Set xattr and total number exceeds max limit.
    */
   @Test
   public void testSetXAttr() throws Exception {
@@ -176,7 +207,7 @@ public class FSXAttrBaseTest {
     Assert.assertArrayEquals(value1, xattrs.get(name1));
     fs.removeXAttr(path, name1);
     
-    //set xattr with null name
+    // Set xattr with null name
     try {
       fs.setXAttr(path, null, value1, EnumSet.of(XAttrSetFlag.CREATE, 
           XAttrSetFlag.REPLACE));
@@ -184,7 +215,7 @@ public class FSXAttrBaseTest {
     } catch (NullPointerException e) {
     }
     
-    //set xattr with empty name: "user."
+    // Set xattr with empty name: "user."
     try {
       fs.setXAttr(path, "user.", value1, EnumSet.of(XAttrSetFlag.CREATE, 
           XAttrSetFlag.REPLACE));
@@ -192,7 +223,7 @@ public class FSXAttrBaseTest {
     } catch (HadoopIllegalArgumentException e) {
     }
     
-    //set xattr with invalid name: "a1"
+    // Set xattr with invalid name: "a1"
     try {
       fs.setXAttr(path, "a1", value1, EnumSet.of(XAttrSetFlag.CREATE, 
           XAttrSetFlag.REPLACE));
@@ -201,14 +232,14 @@ public class FSXAttrBaseTest {
     } catch (HadoopIllegalArgumentException e) {
     }
     
-    //set xattr without XAttrSetFlag
+    // Set xattr without XAttrSetFlag
     fs.setXAttr(path, name1, value1);
     xattrs = fs.getXAttrs(path);
     Assert.assertEquals(xattrs.size(), 1);
     Assert.assertArrayEquals(value1, xattrs.get(name1));
     fs.removeXAttr(path, name1);
     
-    //xattr exists, and replace it using CREATE|REPLACE flag.
+    // XAttr exists, and replace it using CREATE|REPLACE flag.
     fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
     fs.setXAttr(path, name1, newValue1, EnumSet.of(XAttrSetFlag.CREATE, 
         XAttrSetFlag.REPLACE));
@@ -219,7 +250,7 @@ public class FSXAttrBaseTest {
     
     fs.removeXAttr(path, name1);
     
-    //Total number exceeds max limit
+    // Total number exceeds max limit
     fs.setXAttr(path, name1, value1);
     fs.setXAttr(path, name2, value2);
     fs.setXAttr(path, name3, null);
@@ -245,7 +276,7 @@ public class FSXAttrBaseTest {
     fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
     fs.setXAttr(path, name2, value2, EnumSet.of(XAttrSetFlag.CREATE));
     
-    //xattr does not exist.
+    // XAttr does not exist.
     byte[] value = fs.getXAttr(path, name3);
     Assert.assertEquals(value, null);
     
@@ -264,7 +295,8 @@ public class FSXAttrBaseTest {
   
   /**
    * Tests for removing xattr
-   * 1. Remove xattr
+   * 1. Remove xattr.
+   * 2. Restart NN and save checkpoint scenarios.
    */
   @Test
   public void testRemoveXAttr() throws Exception {
@@ -280,9 +312,64 @@ public class FSXAttrBaseTest {
     Assert.assertEquals(xattrs.size(), 1);
     Assert.assertArrayEquals(new byte[0], xattrs.get(name3));
     
+    restart(false);
+    initFileSystem();
+    xattrs = fs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 1);
+    Assert.assertArrayEquals(new byte[0], xattrs.get(name3));
+    
+    restart(true);
+    initFileSystem();
+    xattrs = fs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 1);
+    Assert.assertArrayEquals(new byte[0], xattrs.get(name3));
+    
     fs.removeXAttr(path, name3);
   }
   
+  /**
+   * Steps:
+   * 1) Set xattrs on a file.
+   * 2) Remove xattrs from that file.
+   * 3) Save a checkpoint and restart NN.
+   * 4) Set xattrs again on the same file.
+   * 5) Remove xattrs from that file.
+   * 6) Restart NN without saving a checkpoint.
+   * 7) Set xattrs again on the same file.
+   */
+  @Test
+  public void testCleanupXAttrs() throws Exception {
+    FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750));
+    fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
+    fs.setXAttr(path, name2, value2, EnumSet.of(XAttrSetFlag.CREATE));
+    fs.removeXAttr(path, name1);
+    fs.removeXAttr(path, name2);
+    
+    restart(true);
+    initFileSystem();
+    
+    fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
+    fs.setXAttr(path, name2, value2, EnumSet.of(XAttrSetFlag.CREATE));
+    fs.removeXAttr(path, name1);
+    fs.removeXAttr(path, name2);
+    
+    restart(false);
+    initFileSystem();
+    
+    fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
+    fs.setXAttr(path, name2, value2, EnumSet.of(XAttrSetFlag.CREATE));
+    fs.removeXAttr(path, name1);
+    fs.removeXAttr(path, name2);
+    
+    fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
+    fs.setXAttr(path, name2, value2, EnumSet.of(XAttrSetFlag.CREATE));
+    
+    Map<String, byte[]> xattrs = fs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+  }
+  
   /**
    * Creates a FileSystem for the super-user.
    *
@@ -314,4 +401,20 @@ public class FSXAttrBaseTest {
       .build();
     dfsCluster.waitActive();
   }
+  
+  /**
+   * Restart the cluster, optionally saving a new checkpoint.
+   *
+   * @param checkpoint boolean true to save a new checkpoint
+   * @throws Exception if restart fails
+   */
+  protected static void restart(boolean checkpoint) throws Exception {
+    NameNode nameNode = dfsCluster.getNameNode();
+    if (checkpoint) {
+      NameNodeAdapter.enterSafeMode(nameNode, false);
+      NameNodeAdapter.saveNamespace(nameNode);
+    }
+    shutdown();
+    initCluster(false);
+  }
 }

+ 49 - 0
hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeXAttr.java

@@ -17,15 +17,28 @@
  */
 package org.apache.hadoop.hdfs.server.namenode;
 
+import java.util.Map;
+
 import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.hdfs.DFSConfigKeys;
+import org.apache.hadoop.hdfs.DFSTestUtil;
+import org.junit.Assert;
 import org.junit.BeforeClass;
+import org.junit.Test;
 
 /**
  * Tests NameNode interaction for all XAttr APIs.
+ * This test suite covers restarting NN, saving new checkpoint, 
+ * and also includes test of xattrs for symlinks. 
  */
 public class TestNameNodeXAttr extends FSXAttrBaseTest {
   
+  private static final Path linkParent = new Path("/symdir1");
+  private static final Path targetParent = new Path("/symdir2");
+  private static final Path link = new Path(linkParent, "link");
+  private static final Path target = new Path(targetParent, "target");
+
   @BeforeClass
   public static void init() throws Exception {
     conf = new Configuration();
@@ -34,4 +47,40 @@ public class TestNameNodeXAttr extends FSXAttrBaseTest {
     initCluster(true);
   }
 
+  @Test
+  public void testXAttrSymlinks() throws Exception {
+    fs.mkdirs(linkParent);
+    fs.mkdirs(targetParent);
+    DFSTestUtil.createFile(fs, target, 1024, (short)3, 0xBEEFl);
+    fs.createSymlink(target, link, false);
+    
+    fs.setXAttr(target, name1, value1);
+    fs.setXAttr(target, name2, value2);
+    
+    Map<String, byte[]> xattrs = fs.getXAttrs(link);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+    
+    fs.setXAttr(link, name3, null);
+    xattrs = fs.getXAttrs(target);
+    Assert.assertEquals(xattrs.size(), 3);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+    Assert.assertArrayEquals(new byte[0], xattrs.get(name3));
+    
+    fs.removeXAttr(link, name1);
+    xattrs = fs.getXAttrs(target);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+    Assert.assertArrayEquals(new byte[0], xattrs.get(name3));
+    
+    fs.removeXAttr(target, name3);
+    xattrs = fs.getXAttrs(link);
+    Assert.assertEquals(xattrs.size(), 1);
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+    
+    fs.delete(linkParent, true);
+    fs.delete(targetParent, true);
+  }
 }

+ 150 - 0
hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestXAttrConfigFlag.java

@@ -0,0 +1,150 @@
+/**
+ * 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.hdfs.server.namenode;
+
+import java.io.IOException;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hdfs.DFSConfigKeys;
+import org.apache.hadoop.hdfs.DistributedFileSystem;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.hdfs.server.namenode.NameNode;
+import org.apache.hadoop.hdfs.server.namenode.NameNodeAdapter;
+import org.apache.hadoop.io.IOUtils;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * Tests that the configuration flag that controls support for XAttrs is off
+ * and causes all attempted operations related to XAttrs to fail.  The
+ * NameNode can still load XAttrs from fsimage or edits.
+ */
+public class TestXAttrConfigFlag {
+  private static final Path PATH = new Path("/path");
+
+  private MiniDFSCluster cluster;
+  private DistributedFileSystem fs;
+
+  @Rule
+  public ExpectedException exception = ExpectedException.none();
+
+  @After
+  public void shutdown() throws Exception {
+    IOUtils.cleanup(null, fs);
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  @Test
+  public void testSetXAttr() throws Exception {
+    initCluster(true, false);
+    fs.mkdirs(PATH);
+    expectException();
+    fs.setXAttr(PATH, "user.foo", null);
+  }
+  
+  @Test
+  public void testGetXAttrs() throws Exception {
+    initCluster(true, false);
+    fs.mkdirs(PATH);
+    expectException();
+    fs.getXAttrs(PATH);
+  }
+  
+  @Test
+  public void testRemoveXAttr() throws Exception {
+    initCluster(true, false);
+    fs.mkdirs(PATH);
+    expectException();
+    fs.removeXAttr(PATH, "user.foo");
+  }
+
+  @Test
+  public void testEditLog() throws Exception {
+    // With XAttrs enabled, set an XAttr.
+    initCluster(true, true);
+    fs.mkdirs(PATH);
+    fs.setXAttr(PATH, "user.foo", null);
+
+    // Restart with XAttrs disabled.  Expect successful restart.
+    restart(false, false);
+  }
+
+  @Test
+  public void testFsImage() throws Exception {
+    // With XAttrs enabled, set an XAttr.
+    initCluster(true, true);
+    fs.mkdirs(PATH);
+    fs.setXAttr(PATH, "user.foo", null);
+
+    // Save a new checkpoint and restart with XAttrs still enabled.
+    restart(true, true);
+
+    // Restart with XAttrs disabled.  Expect successful restart.
+    restart(false, false);
+  }
+
+  /**
+   * We expect an IOException, and we want the exception text to state the
+   * configuration key that controls XAttr support.
+   */
+  private void expectException() {
+    exception.expect(IOException.class);
+    exception.expectMessage(DFSConfigKeys.DFS_NAMENODE_XATTRS_ENABLED_KEY);
+  }
+
+  /**
+   * Initialize the cluster, wait for it to become active, and get FileSystem.
+   *
+   * @param format if true, format the NameNode and DataNodes before starting up
+   * @param xattrsEnabled if true, XAttr support is enabled
+   * @throws Exception if any step fails
+   */
+  private void initCluster(boolean format, boolean xattrsEnabled)
+      throws Exception {
+    Configuration conf = new Configuration();
+    // not explicitly setting to false, should be false by default
+    conf.setBoolean(DFSConfigKeys.DFS_NAMENODE_XATTRS_ENABLED_KEY, xattrsEnabled);
+    cluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).format(format)
+      .build();
+    cluster.waitActive();
+    fs = cluster.getFileSystem();
+  }
+
+  /**
+   * Restart the cluster, optionally saving a new checkpoint.
+   *
+   * @param checkpoint boolean true to save a new checkpoint
+   * @param xattrsEnabled if true, XAttr support is enabled
+   * @throws Exception if restart fails
+   */
+  private void restart(boolean checkpoint, boolean xattrsEnabled)
+      throws Exception {
+    NameNode nameNode = cluster.getNameNode();
+    if (checkpoint) {
+      NameNodeAdapter.enterSafeMode(nameNode, false);
+      NameNodeAdapter.saveNamespace(nameNode);
+    }
+    shutdown();
+    initCluster(false, xattrsEnabled);
+  }
+}

+ 114 - 0
hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/ha/TestXAttrsWithHA.java

@@ -0,0 +1,114 @@
+/**
+ * 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.hdfs.server.namenode.ha;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.XAttr;
+import org.apache.hadoop.fs.XAttrSetFlag;
+import org.apache.hadoop.hdfs.DFSConfigKeys;
+import org.apache.hadoop.hdfs.HAUtil;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.hdfs.MiniDFSNNTopology;
+import org.apache.hadoop.hdfs.server.namenode.NameNode;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests interaction of XAttrs with HA failover.
+ */
+public class TestXAttrsWithHA {
+  private static final Path path = new Path("/file");
+  
+  // XAttrs
+  protected static final String name1 = "user.a1";
+  protected static final byte[] value1 = {0x31, 0x32, 0x33};
+  protected static final byte[] newValue1 = {0x31, 0x31, 0x31};
+  protected static final String name2 = "user.a2";
+  protected static final byte[] value2 = {0x37, 0x38, 0x39};
+  protected static final String name3 = "user.a3";
+  
+  private MiniDFSCluster cluster;
+  private NameNode nn0;
+  private NameNode nn1;
+  private FileSystem fs;
+
+  @Before
+  public void setupCluster() throws Exception {
+    Configuration conf = new Configuration();
+    conf.setInt(DFSConfigKeys.DFS_HA_TAILEDITS_PERIOD_KEY, 1);
+    HAUtil.setAllowStandbyReads(conf, true);
+    
+    cluster = new MiniDFSCluster.Builder(conf)
+      .nnTopology(MiniDFSNNTopology.simpleHATopology())
+      .numDataNodes(1)
+      .waitSafeMode(false)
+      .build();
+    cluster.waitActive();
+    
+    nn0 = cluster.getNameNode(0);
+    nn1 = cluster.getNameNode(1);
+    fs = HATestUtil.configureFailoverFs(cluster, conf);
+    
+    cluster.transitionToActive(0);
+  }
+  
+  @After
+  public void shutdownCluster() throws IOException {
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  /**
+   * Test that xattrs are properly tracked by the standby
+   */
+  @Test(timeout = 60000)
+  public void testXAttrsTrackedOnStandby() throws Exception {
+    fs.create(path).close();
+    fs.setXAttr(path, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
+    fs.setXAttr(path, name2, value2, EnumSet.of(XAttrSetFlag.CREATE));
+
+    HATestUtil.waitForStandbyToCatchUp(nn0, nn1);
+    List<XAttr> xAttrs = nn1.getRpcServer().getXAttrs("/file", null);
+    assertEquals(2, xAttrs.size());
+    cluster.shutdownNameNode(0);
+    
+    // Failover the current standby to active.
+    cluster.shutdownNameNode(0);
+    cluster.transitionToActive(1);
+    
+    Map<String, byte[]> xattrs = fs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+    
+    fs.delete(path, true);
+  }
+
+}

+ 273 - 0
hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/snapshot/TestXAttrWithSnapshot.java

@@ -0,0 +1,273 @@
+/**
+ * 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.hdfs.server.namenode.snapshot;
+
+import java.util.Map;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.hdfs.DFSConfigKeys;
+import org.apache.hadoop.hdfs.DistributedFileSystem;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.hdfs.protocol.HdfsConstants;
+import org.apache.hadoop.hdfs.protocol.NSQuotaExceededException;
+import org.apache.hadoop.hdfs.protocol.SnapshotAccessControlException;
+import org.apache.hadoop.hdfs.server.namenode.NameNode;
+import org.apache.hadoop.hdfs.server.namenode.NameNodeAdapter;
+import org.apache.hadoop.io.IOUtils;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * Tests interaction of XAttrs with snapshots.
+ */
+public class TestXAttrWithSnapshot {
+
+  private static MiniDFSCluster cluster;
+  private static Configuration conf;
+  private static DistributedFileSystem hdfs;
+  private static int pathCount = 0;
+  private static Path path, snapshotPath;
+  private static String snapshotName;
+  // XAttrs
+  private static final String name1 = "user.a1";
+  private static final byte[] value1 = { 0x31, 0x32, 0x33 };
+  private static final byte[] newValue1 = { 0x31, 0x31, 0x31 };
+  private static final String name2 = "user.a2";
+  private static final byte[] value2 = { 0x37, 0x38, 0x39 };
+
+  @Rule
+  public ExpectedException exception = ExpectedException.none();
+
+  @BeforeClass
+  public static void init() throws Exception {
+    conf = new Configuration();
+    conf.setBoolean(DFSConfigKeys.DFS_NAMENODE_XATTRS_ENABLED_KEY, true);
+    initCluster(true);
+  }
+
+  @AfterClass
+  public static void shutdown() throws Exception {
+    IOUtils.cleanup(null, hdfs);
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  @Before
+  public void setUp() {
+    ++pathCount;
+    path = new Path("/p" + pathCount);
+    snapshotName = "snapshot" + pathCount;
+    snapshotPath = new Path(path, new Path(".snapshot", snapshotName));
+  }
+
+  /**
+   * 1) Save xattrs, then create snapshot. Assert that inode of original and
+   * snapshot have same xattrs. 2) Change the original xattrs, assert snapshot
+   * still has old xattrs.
+   */
+  @Test
+  public void testXAttrForSnapshotRootAfterChange() throws Exception {
+    FileSystem.mkdirs(hdfs, path, FsPermission.createImmutable((short) 0700));
+    hdfs.setXAttr(path, name1, value1);
+    hdfs.setXAttr(path, name2, value2);
+
+    SnapshotTestHelper.createSnapshot(hdfs, path, snapshotName);
+
+    // Both original and snapshot have same XAttrs.
+    Map<String, byte[]> xattrs = hdfs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+
+    xattrs = hdfs.getXAttrs(snapshotPath);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+
+    // Original XAttrs have changed, but snapshot still has old XAttrs.
+    hdfs.setXAttr(path, name1, newValue1);
+
+    doSnapshotRootChangeAssertions(path, snapshotPath);
+    restart(false);
+    doSnapshotRootChangeAssertions(path, snapshotPath);
+    restart(true);
+    doSnapshotRootChangeAssertions(path, snapshotPath);
+  }
+
+  private static void doSnapshotRootChangeAssertions(Path path,
+      Path snapshotPath) throws Exception {
+    Map<String, byte[]> xattrs = hdfs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(newValue1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+
+    xattrs = hdfs.getXAttrs(snapshotPath);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+  }
+
+  /**
+   * 1) Save xattrs, then create snapshot. Assert that inode of original and
+   * snapshot have same xattrs. 2) Remove some original xattrs, assert snapshot
+   * still has old xattrs.
+   */
+  @Test
+  public void testXAttrForSnapshotRootAfterRemove() throws Exception {
+    FileSystem.mkdirs(hdfs, path, FsPermission.createImmutable((short) 0700));
+    hdfs.setXAttr(path, name1, value1);
+    hdfs.setXAttr(path, name2, value2);
+
+    SnapshotTestHelper.createSnapshot(hdfs, path, snapshotName);
+
+    // Both original and snapshot have same XAttrs.
+    Map<String, byte[]> xattrs = hdfs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+
+    xattrs = hdfs.getXAttrs(snapshotPath);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+
+    // Original XAttrs have been removed, but snapshot still has old XAttrs.
+    hdfs.removeXAttr(path, name1);
+    hdfs.removeXAttr(path, name2);
+
+    doSnapshotRootRemovalAssertions(path, snapshotPath);
+    restart(false);
+    doSnapshotRootRemovalAssertions(path, snapshotPath);
+    restart(true);
+    doSnapshotRootRemovalAssertions(path, snapshotPath);
+  }
+
+  private static void doSnapshotRootRemovalAssertions(Path path,
+      Path snapshotPath) throws Exception {
+    Map<String, byte[]> xattrs = hdfs.getXAttrs(path);
+    Assert.assertEquals(xattrs.size(), 0);
+
+    xattrs = hdfs.getXAttrs(snapshotPath);
+    Assert.assertEquals(xattrs.size(), 2);
+    Assert.assertArrayEquals(value1, xattrs.get(name1));
+    Assert.assertArrayEquals(value2, xattrs.get(name2));
+  }
+
+  /**
+   * Assert exception of setting xattr on read-only snapshot.
+   */
+  @Test
+  public void testSetXAttrSnapshotPath() throws Exception {
+    FileSystem.mkdirs(hdfs, path, FsPermission.createImmutable((short) 0700));
+    SnapshotTestHelper.createSnapshot(hdfs, path, snapshotName);
+    exception.expect(SnapshotAccessControlException.class);
+    hdfs.setXAttr(snapshotPath, name1, value1);
+  }
+
+  /**
+   * Assert exception of setting xattr when exceeding quota.
+   */
+  @Test
+  public void testSetXAttrExceedsQuota() throws Exception {
+    Path filePath = new Path(path, "file1");
+    Path fileSnapshotPath = new Path(snapshotPath, "file1");
+    FileSystem.mkdirs(hdfs, path, FsPermission.createImmutable((short) 0755));
+    hdfs.allowSnapshot(path);
+    hdfs.setQuota(path, 3, HdfsConstants.QUOTA_DONT_SET);
+    FileSystem.create(hdfs, filePath,
+        FsPermission.createImmutable((short) 0600)).close();
+    hdfs.setXAttr(filePath, name1, value1);
+
+    hdfs.createSnapshot(path, snapshotName);
+
+    byte[] value = hdfs.getXAttr(filePath, name1);
+    Assert.assertArrayEquals(value, value1);
+
+    value = hdfs.getXAttr(fileSnapshotPath, name1);
+    Assert.assertArrayEquals(value, value1);
+
+    exception.expect(NSQuotaExceededException.class);
+    hdfs.setXAttr(filePath, name2, value2);
+  }
+
+  /**
+   * Assert exception of removing xattr when exceeding quota.
+   */
+  @Test
+  public void testRemoveXAttrExceedsQuota() throws Exception {
+    Path filePath = new Path(path, "file1");
+    Path fileSnapshotPath = new Path(snapshotPath, "file1");
+    FileSystem.mkdirs(hdfs, path, FsPermission.createImmutable((short) 0755));
+    hdfs.allowSnapshot(path);
+    hdfs.setQuota(path, 3, HdfsConstants.QUOTA_DONT_SET);
+    FileSystem.create(hdfs, filePath,
+        FsPermission.createImmutable((short) 0600)).close();
+    hdfs.setXAttr(filePath, name1, value1);
+
+    hdfs.createSnapshot(path, snapshotName);
+
+    byte[] value = hdfs.getXAttr(filePath, name1);
+    Assert.assertArrayEquals(value, value1);
+
+    value = hdfs.getXAttr(fileSnapshotPath, name1);
+    Assert.assertArrayEquals(value, value1);
+
+    exception.expect(NSQuotaExceededException.class);
+    hdfs.removeXAttr(filePath, name1);
+  }
+
+  /**
+   * Initialize the cluster, wait for it to become active, and get FileSystem
+   * instances for our test users.
+   * 
+   * @param format if true, format the NameNode and DataNodes before starting up
+   * @throws Exception if any step fails
+   */
+  private static void initCluster(boolean format) throws Exception {
+    cluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).format(format)
+        .build();
+    cluster.waitActive();
+    hdfs = cluster.getFileSystem();
+  }
+
+  /**
+   * Restart the cluster, optionally saving a new checkpoint.
+   * 
+   * @param checkpoint boolean true to save a new checkpoint
+   * @throws Exception if restart fails
+   */
+  private static void restart(boolean checkpoint) throws Exception {
+    NameNode nameNode = cluster.getNameNode();
+    if (checkpoint) {
+      NameNodeAdapter.enterSafeMode(nameNode, false);
+      NameNodeAdapter.saveNamespace(nameNode);
+    }
+    shutdown();
+    initCluster(false);
+  }
+}