/** * 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; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.CommonConfigurationKeys; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.TestTrash; import org.apache.hadoop.fs.Trash; import org.apache.hadoop.fs.permission.FsAction; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.security.AccessControlException; import org.apache.hadoop.security.UserGroupInformation; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; /** * Test trash using HDFS */ public class TestHDFSTrash { public static final Log LOG = LogFactory.getLog(TestHDFSTrash.class); private static MiniDFSCluster cluster = null; private static FileSystem fs; private static Configuration conf = new HdfsConfiguration(); private final static Path TEST_ROOT = new Path("/TestHDFSTrash-ROOT"); private final static Path TRASH_ROOT = new Path("/TestHDFSTrash-TRASH"); final private static String GROUP1_NAME = "group1"; final private static String GROUP2_NAME = "group2"; final private static String GROUP3_NAME = "group3"; final private static String USER1_NAME = "user1"; final private static String USER2_NAME = "user2"; private static UserGroupInformation superUser; private static UserGroupInformation user1; private static UserGroupInformation user2; @BeforeClass public static void setUp() throws Exception { cluster = new MiniDFSCluster.Builder(conf).numDataNodes(2).build(); fs = FileSystem.get(conf); superUser = UserGroupInformation.getCurrentUser(); user1 = UserGroupInformation.createUserForTesting(USER1_NAME, new String[] {GROUP1_NAME, GROUP2_NAME}); user2 = UserGroupInformation.createUserForTesting(USER2_NAME, new String[] {GROUP2_NAME, GROUP3_NAME}); // Init test and trash root dirs in HDFS fs.mkdirs(TEST_ROOT); fs.setPermission(TEST_ROOT, new FsPermission((short) 0777)); DFSTestUtil.verifyFilePermission( fs.getFileStatus(TEST_ROOT), superUser.getShortUserName(), null, FsAction.ALL, FsAction.ALL, FsAction.ALL); fs.mkdirs(TRASH_ROOT); fs.setPermission(TRASH_ROOT, new FsPermission((short) 0777)); DFSTestUtil.verifyFilePermission( fs.getFileStatus(TRASH_ROOT), superUser.getShortUserName(), null, FsAction.ALL, FsAction.ALL, FsAction.ALL); } @AfterClass public static void tearDown() { if (cluster != null) { cluster.shutdown(); } } @Test public void testTrash() throws IOException { TestTrash.trashShell(cluster.getFileSystem(), new Path("/")); } @Test public void testNonDefaultFS() throws IOException { FileSystem fileSystem = cluster.getFileSystem(); Configuration config = fileSystem.getConf(); config.set(CommonConfigurationKeys.FS_DEFAULT_NAME_KEY, fileSystem.getUri().toString()); TestTrash.trashNonDefaultFS(config); } @Test public void testHDFSTrashPermission() throws IOException { FileSystem fileSystem = cluster.getFileSystem(); Configuration config = fileSystem.getConf(); config.set(CommonConfigurationKeys.FS_TRASH_INTERVAL_KEY, "0.2"); TestTrash.verifyTrashPermission(fileSystem, config); } @Test public void testMoveEmptyDirToTrash() throws IOException { FileSystem fileSystem = cluster.getFileSystem(); Configuration config = fileSystem.getConf(); config.set(CommonConfigurationKeys.FS_TRASH_INTERVAL_KEY, "1"); TestTrash.verifyMoveEmptyDirToTrash(fileSystem, config); } @Test public void testDeleteTrash() throws Exception { Configuration testConf = new Configuration(conf); testConf.set(CommonConfigurationKeys.FS_TRASH_INTERVAL_KEY, "10"); Path user1Tmp = new Path(TEST_ROOT, "test-del-u1"); Path user2Tmp = new Path(TEST_ROOT, "test-del-u2"); // login as user1, move something to trash // verify user1 can remove its own trash dir fs = DFSTestUtil.login(fs, testConf, user1); fs.mkdirs(user1Tmp); Trash u1Trash = getPerUserTrash(user1, fs, testConf); Path u1t = u1Trash.getCurrentTrashDir(user1Tmp); assertTrue(String.format("Failed to move %s to trash", user1Tmp), u1Trash.moveToTrash(user1Tmp)); assertTrue( String.format( "%s should be allowed to remove its own trash directory %s", user1.getUserName(), u1t), fs.delete(u1t, true)); assertFalse(fs.exists(u1t)); // login as user2, move something to trash fs = DFSTestUtil.login(fs, testConf, user2); fs.mkdirs(user2Tmp); Trash u2Trash = getPerUserTrash(user2, fs, testConf); u2Trash.moveToTrash(user2Tmp); Path u2t = u2Trash.getCurrentTrashDir(user2Tmp); try { // user1 should not be able to remove user2's trash dir fs = DFSTestUtil.login(fs, testConf, user1); fs.delete(u2t, true); fail(String.format("%s should not be able to remove %s trash directory", USER1_NAME, USER2_NAME)); } catch (AccessControlException e) { assertTrue(e instanceof AccessControlException); assertTrue("Permission denied messages must carry the username", e.getMessage().contains(USER1_NAME)); } } /** * Return a {@link Trash} instance using giving configuration. * The trash root directory is set to an unique directory under * {@link #TRASH_ROOT}. Use this method to isolate trash * directories for different users. */ private Trash getPerUserTrash(UserGroupInformation ugi, FileSystem fileSystem, Configuration config) throws IOException { // generate an unique path per instance UUID trashId = UUID.randomUUID(); StringBuffer sb = new StringBuffer() .append(ugi.getUserName()) .append("-") .append(trashId.toString()); Path userTrashRoot = new Path(TRASH_ROOT, sb.toString()); FileSystem spyUserFs = Mockito.spy(fileSystem); Mockito.when(spyUserFs.getTrashRoot(Mockito.any(Path.class))) .thenReturn(userTrashRoot); return new Trash(spyUserFs, config); } }