/** * 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 static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import java.util.EnumSet; import java.util.Map; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.FsShell; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.XAttrSetFlag; 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.apache.hadoop.util.ToolRunner; 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, snapshotPath2, snapshotPath3; private static String snapshotName, snapshotName2, snapshotName3; private final int SUCCESS = 0; // 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; snapshotName2 = snapshotName + "-2"; snapshotName3 = snapshotName + "-3"; snapshotPath = new Path(path, new Path(".snapshot", snapshotName)); snapshotPath2 = new Path(path, new Path(".snapshot", snapshotName2)); snapshotPath3 = new Path(path, new Path(".snapshot", snapshotName3)); } /** * Tests modifying xattrs on a directory that has been snapshotted */ @Test (timeout = 120000) public void testModifyReadsCurrentState() throws Exception { // Init FileSystem.mkdirs(hdfs, path, FsPermission.createImmutable((short) 0700)); SnapshotTestHelper.createSnapshot(hdfs, path, snapshotName); hdfs.setXAttr(path, name1, value1); hdfs.setXAttr(path, name2, value2); // Verify that current path reflects xattrs, snapshot doesn't Map<String, byte[]> xattrs = hdfs.getXAttrs(path); assertEquals(xattrs.size(), 2); assertArrayEquals(value1, xattrs.get(name1)); assertArrayEquals(value2, xattrs.get(name2)); xattrs = hdfs.getXAttrs(snapshotPath); assertEquals(xattrs.size(), 0); // Modify each xattr and make sure it's reflected hdfs.setXAttr(path, name1, value2, EnumSet.of(XAttrSetFlag.REPLACE)); xattrs = hdfs.getXAttrs(path); assertEquals(xattrs.size(), 2); assertArrayEquals(value2, xattrs.get(name1)); assertArrayEquals(value2, xattrs.get(name2)); hdfs.setXAttr(path, name2, value1, EnumSet.of(XAttrSetFlag.REPLACE)); xattrs = hdfs.getXAttrs(path); assertEquals(xattrs.size(), 2); assertArrayEquals(value2, xattrs.get(name1)); assertArrayEquals(value1, xattrs.get(name2)); // Paranoia checks xattrs = hdfs.getXAttrs(snapshotPath); assertEquals(xattrs.size(), 0); hdfs.removeXAttr(path, name1); hdfs.removeXAttr(path, name2); xattrs = hdfs.getXAttrs(path); assertEquals(xattrs.size(), 0); } /** * Tests removing xattrs on a directory that has been snapshotted */ @Test (timeout = 120000) public void testRemoveReadsCurrentState() throws Exception { // Init FileSystem.mkdirs(hdfs, path, FsPermission.createImmutable((short) 0700)); SnapshotTestHelper.createSnapshot(hdfs, path, snapshotName); hdfs.setXAttr(path, name1, value1); hdfs.setXAttr(path, name2, value2); // Verify that current path reflects xattrs, snapshot doesn't Map<String, byte[]> xattrs = hdfs.getXAttrs(path); assertEquals(xattrs.size(), 2); assertArrayEquals(value1, xattrs.get(name1)); assertArrayEquals(value2, xattrs.get(name2)); xattrs = hdfs.getXAttrs(snapshotPath); assertEquals(xattrs.size(), 0); // Remove xattrs and verify one-by-one hdfs.removeXAttr(path, name2); xattrs = hdfs.getXAttrs(path); assertEquals(xattrs.size(), 1); assertArrayEquals(value1, xattrs.get(name1)); hdfs.removeXAttr(path, name1); xattrs = hdfs.getXAttrs(path); assertEquals(xattrs.size(), 0); } /** * 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(0, xattrs.size()); xattrs = hdfs.getXAttrs(snapshotPath); Assert.assertEquals(2, xattrs.size()); Assert.assertArrayEquals(value1, xattrs.get(name1)); Assert.assertArrayEquals(value2, xattrs.get(name2)); } /** * Test successive snapshots in between modifications of XAttrs. * Also verify that snapshot XAttrs are not altered when a * snapshot is deleted. */ @Test public void testSuccessiveSnapshotXAttrChanges() throws Exception { // First snapshot FileSystem.mkdirs(hdfs, path, FsPermission.createImmutable((short) 0700)); hdfs.setXAttr(path, name1, value1); SnapshotTestHelper.createSnapshot(hdfs, path, snapshotName); Map<String, byte[]> xattrs = hdfs.getXAttrs(snapshotPath); Assert.assertEquals(1, xattrs.size()); Assert.assertArrayEquals(value1, xattrs.get(name1)); // Second snapshot hdfs.setXAttr(path, name1, newValue1); hdfs.setXAttr(path, name2, value2); SnapshotTestHelper.createSnapshot(hdfs, path, snapshotName2); xattrs = hdfs.getXAttrs(snapshotPath2); Assert.assertEquals(2, xattrs.size()); Assert.assertArrayEquals(newValue1, xattrs.get(name1)); Assert.assertArrayEquals(value2, xattrs.get(name2)); // Third snapshot hdfs.setXAttr(path, name1, value1); hdfs.removeXAttr(path, name2); SnapshotTestHelper.createSnapshot(hdfs, path, snapshotName3); xattrs = hdfs.getXAttrs(snapshotPath3); Assert.assertEquals(1, xattrs.size()); Assert.assertArrayEquals(value1, xattrs.get(name1)); // Check that the first and second snapshots' // XAttrs have stayed constant xattrs = hdfs.getXAttrs(snapshotPath); Assert.assertEquals(1, xattrs.size()); Assert.assertArrayEquals(value1, xattrs.get(name1)); xattrs = hdfs.getXAttrs(snapshotPath2); Assert.assertEquals(2, xattrs.size()); Assert.assertArrayEquals(newValue1, xattrs.get(name1)); Assert.assertArrayEquals(value2, xattrs.get(name2)); // Remove the second snapshot and verify the first and // third snapshots' XAttrs have stayed constant hdfs.deleteSnapshot(path, snapshotName2); xattrs = hdfs.getXAttrs(snapshotPath); Assert.assertEquals(1, xattrs.size()); Assert.assertArrayEquals(value1, xattrs.get(name1)); xattrs = hdfs.getXAttrs(snapshotPath3); Assert.assertEquals(1, xattrs.size()); Assert.assertArrayEquals(value1, xattrs.get(name1)); hdfs.deleteSnapshot(path, snapshotName); hdfs.deleteSnapshot(path, snapshotName3); } /** * 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 removing xattr on read-only snapshot. */ @Test public void testRemoveXAttrSnapshotPath() throws Exception { FileSystem.mkdirs(hdfs, path, FsPermission.createImmutable((short) 0700)); hdfs.setXAttr(path, name1, value1); SnapshotTestHelper.createSnapshot(hdfs, path, snapshotName); exception.expect(SnapshotAccessControlException.class); hdfs.removeXAttr(snapshotPath, name1); } /** * Test that users can copy a snapshot while preserving its xattrs. */ @Test (timeout = 120000) public void testCopySnapshotShouldPreserveXAttrs() 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); Path snapshotCopy = new Path(path.toString() + "-copy"); String[] argv = new String[] { "-cp", "-px", snapshotPath.toUri().toString(), snapshotCopy.toUri().toString() }; int ret = ToolRunner.run(new FsShell(conf), argv); assertEquals("cp -px is not working on a snapshot", SUCCESS, ret); Map<String, byte[]> xattrs = hdfs.getXAttrs(snapshotCopy); assertArrayEquals(value1, xattrs.get(name1)); assertArrayEquals(value2, xattrs.get(name2)); } /** * 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); } }