/** * 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.assertEquals; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.net.URI; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.FileSystemTestHelper; import org.apache.hadoop.fs.FsShell; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.security.UserGroupInformation; import org.junit.Test; /** * This test covers privilege related aspects of FsShell * */ public class TestFsShellPermission { static private final String TEST_ROOT = "/testroot"; static UserGroupInformation createUGI(String ownername, String groupName) { return UserGroupInformation.createUserForTesting(ownername, new String[]{groupName}); } private class FileEntry { private String path; private boolean isDir; private String owner; private String group; private String permission; public FileEntry(String path, boolean isDir, String owner, String group, String permission) { this.path = path; this.isDir = isDir; this.owner = owner; this.group = group; this.permission = permission; } String getPath() { return path; } boolean isDirectory() { return isDir; } String getOwner() { return owner; } String getGroup() { return group; } String getPermission() { return permission; } } private void createFiles(FileSystem fs, String topdir, FileEntry[] entries) throws IOException { for (FileEntry entry : entries) { String newPathStr = topdir + "/" + entry.getPath(); Path newPath = new Path(newPathStr); if (entry.isDirectory()) { fs.mkdirs(newPath); } else { FileSystemTestHelper.createFile(fs, newPath); } fs.setPermission(newPath, new FsPermission(entry.getPermission())); fs.setOwner(newPath, entry.getOwner(), entry.getGroup()); } } /** delete directory and everything underneath it.*/ private static void deldir(FileSystem fs, String topdir) throws IOException { fs.delete(new Path(topdir), true); } static String execCmd(FsShell shell, final String[] args) throws Exception { ByteArrayOutputStream baout = new ByteArrayOutputStream(); PrintStream out = new PrintStream(baout, true); PrintStream old = System.out; System.setOut(out); int ret = shell.run(args); out.close(); System.setOut(old); return String.valueOf(ret); } /* * Each instance of TestDeleteHelper captures one testing scenario. * * To create all files listed in fileEntries, and then delete as user * doAsuser the deleteEntry with command+options specified in cmdAndOptions. * * When expectedToDelete is true, the deleteEntry is expected to be deleted; * otherwise, it's not expected to be deleted. At the end of test, * the existence of deleteEntry is checked against expectedToDelete * to ensure the command is finished with expected result */ private class TestDeleteHelper { private FileEntry[] fileEntries; private FileEntry deleteEntry; private String cmdAndOptions; private boolean expectedToDelete; final String doAsGroup; final UserGroupInformation userUgi; public TestDeleteHelper( FileEntry[] fileEntries, FileEntry deleteEntry, String cmdAndOptions, String doAsUser, boolean expectedToDelete) { this.fileEntries = fileEntries; this.deleteEntry = deleteEntry; this.cmdAndOptions = cmdAndOptions; this.expectedToDelete = expectedToDelete; doAsGroup = doAsUser.equals("hdfs")? "supergroup" : "users"; userUgi = createUGI(doAsUser, doAsGroup); } public void execute(Configuration conf, FileSystem fs) throws Exception { fs.mkdirs(new Path(TEST_ROOT)); createFiles(fs, TEST_ROOT, fileEntries); final FsShell fsShell = new FsShell(conf); final String deletePath = TEST_ROOT + "/" + deleteEntry.getPath(); String[] tmpCmdOpts = StringUtils.split(cmdAndOptions); ArrayList<String> tmpArray = new ArrayList<String>(Arrays.asList(tmpCmdOpts)); tmpArray.add(deletePath); final String[] cmdOpts = tmpArray.toArray(new String[tmpArray.size()]); userUgi.doAs(new PrivilegedExceptionAction<String>() { public String run() throws Exception { return execCmd(fsShell, cmdOpts); } }); boolean deleted = !fs.exists(new Path(deletePath)); assertEquals(expectedToDelete, deleted); deldir(fs, TEST_ROOT); } } private TestDeleteHelper genDeleteEmptyDirHelper(final String cmdOpts, final String targetPerm, final String asUser, boolean expectedToDelete) { FileEntry[] files = { new FileEntry("userA", true, "userA", "users", "755"), new FileEntry("userA/userB", true, "userB", "users", targetPerm) }; FileEntry deleteEntry = files[1]; return new TestDeleteHelper(files, deleteEntry, cmdOpts, asUser, expectedToDelete); } // Expect target to be deleted private TestDeleteHelper genRmrEmptyDirWithReadPerm() { return genDeleteEmptyDirHelper("-rm -r", "744", "userA", true); } // Expect target to be deleted private TestDeleteHelper genRmrEmptyDirWithNoPerm() { return genDeleteEmptyDirHelper("-rm -r", "700", "userA", true); } // Expect target to be deleted private TestDeleteHelper genRmrfEmptyDirWithNoPerm() { return genDeleteEmptyDirHelper("-rm -r -f", "700", "userA", true); } private TestDeleteHelper genDeleteNonEmptyDirHelper(final String cmd, final String targetPerm, final String asUser, boolean expectedToDelete) { FileEntry[] files = { new FileEntry("userA", true, "userA", "users", "755"), new FileEntry("userA/userB", true, "userB", "users", targetPerm), new FileEntry("userA/userB/xyzfile", false, "userB", "users", targetPerm) }; FileEntry deleteEntry = files[1]; return new TestDeleteHelper(files, deleteEntry, cmd, asUser, expectedToDelete); } // Expect target not to be deleted private TestDeleteHelper genRmrNonEmptyDirWithReadPerm() { return genDeleteNonEmptyDirHelper("-rm -r", "744", "userA", false); } // Expect target not to be deleted private TestDeleteHelper genRmrNonEmptyDirWithNoPerm() { return genDeleteNonEmptyDirHelper("-rm -r", "700", "userA", false); } // Expect target to be deleted private TestDeleteHelper genRmrNonEmptyDirWithAllPerm() { return genDeleteNonEmptyDirHelper("-rm -r", "777", "userA", true); } // Expect target not to be deleted private TestDeleteHelper genRmrfNonEmptyDirWithNoPerm() { return genDeleteNonEmptyDirHelper("-rm -r -f", "700", "userA", false); } // Expect target to be deleted public TestDeleteHelper genDeleteSingleFileNotAsOwner() throws Exception { FileEntry[] files = { new FileEntry("userA", true, "userA", "users", "755"), new FileEntry("userA/userB", false, "userB", "users", "700") }; FileEntry deleteEntry = files[1]; return new TestDeleteHelper(files, deleteEntry, "-rm -r", "userA", true); } @Test public void testDelete() throws Exception { Configuration conf = null; MiniDFSCluster cluster = null; try { conf = new Configuration(); cluster = new MiniDFSCluster.Builder(conf).numDataNodes(2).build(); String nnUri = FileSystem.getDefaultUri(conf).toString(); FileSystem fs = FileSystem.get(URI.create(nnUri), conf); ArrayList<TestDeleteHelper> ta = new ArrayList<TestDeleteHelper>(); // Add empty dir tests ta.add(genRmrEmptyDirWithReadPerm()); ta.add(genRmrEmptyDirWithNoPerm()); ta.add(genRmrfEmptyDirWithNoPerm()); // Add non-empty dir tests ta.add(genRmrNonEmptyDirWithReadPerm()); ta.add(genRmrNonEmptyDirWithNoPerm()); ta.add(genRmrNonEmptyDirWithAllPerm()); ta.add(genRmrfNonEmptyDirWithNoPerm()); // Add single tile test ta.add(genDeleteSingleFileNotAsOwner()); // Run all tests for(TestDeleteHelper t : ta) { t.execute(conf, fs); } } finally { if (cluster != null) { cluster.shutdown(); } } } }