| /** |
| * 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.apache.hadoop.fs.CommonConfigurationKeys.*; |
| import static org.apache.hadoop.fs.FileSystemTestHelper.*; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.net.URI; |
| import java.text.DateFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.HashSet; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import junit.framework.TestCase; |
| |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.TrashPolicyDefault.Emptier; |
| import org.apache.hadoop.fs.permission.FsPermission; |
| import org.apache.hadoop.test.GenericTestUtils; |
| import org.apache.hadoop.util.Time; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** |
| * This class tests commands from Trash. |
| */ |
| public class TestTrash extends TestCase { |
| |
| private final static File BASE_PATH = new File(GenericTestUtils.getTempPath( |
| "testTrash")); |
| |
| private final static Path TEST_DIR = new Path(BASE_PATH.getAbsolutePath()); |
| |
| @Before |
| public void setUp() throws IOException { |
| // ensure each test initiates a FileSystem instance, |
| // avoid getting an old instance from cache. |
| FileSystem.closeAll(); |
| } |
| |
| protected static Path mkdir(FileSystem fs, Path p) throws IOException { |
| assertTrue(fs.mkdirs(p)); |
| assertTrue(fs.exists(p)); |
| assertTrue(fs.getFileStatus(p).isDirectory()); |
| return p; |
| } |
| |
| // check that the specified file is in Trash |
| protected static void checkTrash(FileSystem trashFs, Path trashRoot, |
| Path path) throws IOException { |
| Path p = Path.mergePaths(trashRoot, path); |
| assertTrue("Could not find file in trash: "+ p , trashFs.exists(p)); |
| } |
| |
| // counts how many instances of the file are in the Trash |
| // they all are in format fileName* |
| protected static int countSameDeletedFiles(FileSystem fs, |
| Path trashDir, Path fileName) throws IOException { |
| |
| final String prefix = fileName.getName(); |
| System.out.println("Counting " + fileName + " in " + trashDir.toString()); |
| |
| // filter that matches all the files that start with fileName* |
| PathFilter pf = new PathFilter() { |
| @Override |
| public boolean accept(Path file) { |
| return file.getName().startsWith(prefix); |
| } |
| }; |
| // run the filter |
| FileStatus [] fss = fs.listStatus(trashDir, pf); |
| |
| return fss==null? 0 : fss.length; |
| } |
| |
| // check that the specified file is not in Trash |
| static void checkNotInTrash(FileSystem fs, Path trashRoot, String pathname) |
| throws IOException { |
| Path p = new Path(trashRoot+"/"+ new Path(pathname).getName()); |
| assertTrue(!fs.exists(p)); |
| } |
| |
| /** |
| * Test trash for the shell's delete command for the file system fs |
| * @param fs |
| * @param base - the base path where files are created |
| * @throws IOException |
| */ |
| public static void trashShell(final FileSystem fs, final Path base) |
| throws IOException { |
| Configuration conf = new Configuration(); |
| conf.set("fs.defaultFS", fs.getUri().toString()); |
| trashShell(conf, base, null, null); |
| } |
| |
| /** |
| * Test trash for the shell's delete command for the default file system |
| * specified in the paramter conf |
| * @param conf |
| * @param base - the base path where files are created |
| * @param trashRoot - the expected place where the trashbin resides |
| * @throws IOException |
| */ |
| public static void trashShell(final Configuration conf, final Path base, |
| FileSystem trashRootFs, Path trashRoot) |
| throws IOException { |
| FileSystem fs = FileSystem.get(conf); |
| |
| conf.setLong(FS_TRASH_INTERVAL_KEY, 0); // disabled |
| assertFalse(new Trash(conf).isEnabled()); |
| |
| conf.setLong(FS_TRASH_INTERVAL_KEY, 10); // 10 minute |
| assertTrue(new Trash(conf).isEnabled()); |
| |
| FsShell shell = new FsShell(); |
| shell.setConf(conf); |
| if (trashRoot == null) { |
| trashRoot = shell.getCurrentTrashDir(); |
| } |
| if (trashRootFs == null) { |
| trashRootFs = fs; |
| } |
| |
| // First create a new directory with mkdirs |
| Path myPath = new Path(base, "test/mkdirs"); |
| mkdir(fs, myPath); |
| |
| // Second, create a file in that directory. |
| Path myFile = new Path(base, "test/mkdirs/myFile"); |
| writeFile(fs, myFile, 10); |
| |
| // Verify that expunge without Trash directory |
| // won't throw Exception |
| { |
| String[] args = new String[1]; |
| args[0] = "-expunge"; |
| int val = -1; |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(val == 0); |
| } |
| |
| // Verify that we succeed in removing the file we created. |
| // This should go into Trash. |
| { |
| String[] args = new String[2]; |
| args[0] = "-rm"; |
| args[1] = myFile.toString(); |
| int val = -1; |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(val == 0); |
| |
| |
| checkTrash(trashRootFs, trashRoot, fs.makeQualified(myFile)); |
| } |
| |
| // Verify that we can recreate the file |
| writeFile(fs, myFile, 10); |
| |
| // Verify that we succeed in removing the file we re-created |
| { |
| String[] args = new String[2]; |
| args[0] = "-rm"; |
| args[1] = new Path(base, "test/mkdirs/myFile").toString(); |
| int val = -1; |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(val == 0); |
| } |
| |
| // Verify that we can recreate the file |
| writeFile(fs, myFile, 10); |
| |
| // Verify that we succeed in removing the whole directory |
| // along with the file inside it. |
| { |
| String[] args = new String[2]; |
| args[0] = "-rmr"; |
| args[1] = new Path(base, "test/mkdirs").toString(); |
| int val = -1; |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(val == 0); |
| } |
| |
| // recreate directory |
| mkdir(fs, myPath); |
| |
| // Verify that we succeed in removing the whole directory |
| { |
| String[] args = new String[2]; |
| args[0] = "-rmr"; |
| args[1] = new Path(base, "test/mkdirs").toString(); |
| int val = -1; |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(val == 0); |
| } |
| |
| // Check that we can delete a file from the trash |
| { |
| Path toErase = new Path(trashRoot, "toErase"); |
| int retVal = -1; |
| writeFile(trashRootFs, toErase, 10); |
| try { |
| retVal = shell.run(new String[] {"-rm", toErase.toString()}); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(retVal == 0); |
| checkNotInTrash (trashRootFs, trashRoot, toErase.toString()); |
| checkNotInTrash (trashRootFs, trashRoot, toErase.toString()+".1"); |
| } |
| |
| // simulate Trash removal |
| { |
| String[] args = new String[1]; |
| args[0] = "-expunge"; |
| int val = -1; |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(val == 0); |
| } |
| |
| // verify that after expunging the Trash, it really goes away |
| checkNotInTrash(trashRootFs, trashRoot, new Path(base, "test/mkdirs/myFile").toString()); |
| |
| // recreate directory and file |
| mkdir(fs, myPath); |
| writeFile(fs, myFile, 10); |
| |
| // remove file first, then remove directory |
| { |
| String[] args = new String[2]; |
| args[0] = "-rm"; |
| args[1] = myFile.toString(); |
| int val = -1; |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(val == 0); |
| checkTrash(trashRootFs, trashRoot, myFile); |
| |
| args = new String[2]; |
| args[0] = "-rmr"; |
| args[1] = myPath.toString(); |
| val = -1; |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(val == 0); |
| checkTrash(trashRootFs, trashRoot, myPath); |
| } |
| |
| // attempt to remove parent of trash |
| { |
| String[] args = new String[2]; |
| args[0] = "-rmr"; |
| args[1] = trashRoot.getParent().getParent().toString(); |
| int val = -1; |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertEquals("exit code", 1, val); |
| assertTrue(trashRootFs.exists(trashRoot)); |
| } |
| |
| // Verify skip trash option really works |
| |
| // recreate directory and file |
| mkdir(fs, myPath); |
| writeFile(fs, myFile, 10); |
| |
| // Verify that skip trash option really skips the trash for files (rm) |
| { |
| String[] args = new String[3]; |
| args[0] = "-rm"; |
| args[1] = "-skipTrash"; |
| args[2] = myFile.toString(); |
| int val = -1; |
| try { |
| // Clear out trash |
| assertEquals("-expunge failed", |
| 0, shell.run(new String [] { "-expunge" } )); |
| |
| val = shell.run(args); |
| |
| }catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertFalse("Expected TrashRoot (" + trashRoot + |
| ") to exist in file system:" |
| + trashRootFs.getUri(), |
| trashRootFs.exists(trashRoot)); // No new Current should be created |
| assertFalse(fs.exists(myFile)); |
| assertTrue(val == 0); |
| } |
| |
| // recreate directory and file |
| mkdir(fs, myPath); |
| writeFile(fs, myFile, 10); |
| |
| // Verify that skip trash option really skips the trash for rmr |
| { |
| String[] args = new String[3]; |
| args[0] = "-rmr"; |
| args[1] = "-skipTrash"; |
| args[2] = myPath.toString(); |
| |
| int val = -1; |
| try { |
| // Clear out trash |
| assertEquals(0, shell.run(new String [] { "-expunge" } )); |
| |
| val = shell.run(args); |
| |
| }catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| |
| assertFalse(trashRootFs.exists(trashRoot)); // No new Current should be created |
| assertFalse(fs.exists(myPath)); |
| assertFalse(fs.exists(myFile)); |
| assertTrue(val == 0); |
| } |
| |
| // deleting same file multiple times |
| { |
| int val = -1; |
| mkdir(fs, myPath); |
| |
| try { |
| assertEquals(0, shell.run(new String [] { "-expunge" } )); |
| } catch (Exception e) { |
| System.err.println("Exception raised from fs expunge " + |
| e.getLocalizedMessage()); |
| } |
| |
| // create a file in that directory. |
| myFile = new Path(base, "test/mkdirs/myFile"); |
| String [] args = new String[] {"-rm", myFile.toString()}; |
| int num_runs = 10; |
| for(int i=0;i<num_runs; i++) { |
| |
| //create file |
| writeFile(fs, myFile, 10); |
| |
| // delete file |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(val==0); |
| } |
| // current trash directory |
| Path trashDir = Path.mergePaths(new Path(trashRoot.toUri().getPath()), |
| new Path(myFile.getParent().toUri().getPath())); |
| |
| System.out.println("Deleting same myFile: myFile.parent=" + myFile.getParent().toUri().getPath() + |
| "; trashroot="+trashRoot.toUri().getPath() + |
| "; trashDir=" + trashDir.toUri().getPath()); |
| |
| int count = countSameDeletedFiles(fs, trashDir, myFile); |
| System.out.println("counted " + count + " files " + myFile.getName() + "* in " + trashDir); |
| assertTrue(count==num_runs); |
| } |
| |
| //Verify skipTrash option is suggested when rm fails due to its absence |
| { |
| String[] args = new String[2]; |
| args[0] = "-rmr"; |
| args[1] = "/"; //This always contains trash directory |
| PrintStream stdout = System.out; |
| PrintStream stderr = System.err; |
| ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); |
| PrintStream newOut = new PrintStream(byteStream); |
| System.setOut(newOut); |
| System.setErr(newOut); |
| try { |
| shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| String output = byteStream.toString(); |
| System.setOut(stdout); |
| System.setErr(stderr); |
| assertTrue("skipTrash wasn't suggested as remedy to failed rm command" + |
| " or we deleted / even though we could not get server defaults", |
| output.indexOf("Consider using -skipTrash option") != -1 || |
| output.indexOf("Failed to determine server trash configuration") != -1); |
| } |
| |
| // Verify old checkpoint format is recognized |
| { |
| // emulate two old trash checkpoint directories, one that is old enough |
| // to be deleted on the next expunge and one that isn't. |
| long trashInterval = conf.getLong(FS_TRASH_INTERVAL_KEY, |
| FS_TRASH_INTERVAL_DEFAULT); |
| long now = Time.now(); |
| DateFormat oldCheckpointFormat = new SimpleDateFormat("yyMMddHHmm"); |
| Path dirToDelete = new Path(trashRoot.getParent(), |
| oldCheckpointFormat.format(now - (trashInterval * 60 * 1000) - 1)); |
| Path dirToKeep = new Path(trashRoot.getParent(), |
| oldCheckpointFormat.format(now)); |
| mkdir(trashRootFs, dirToDelete); |
| mkdir(trashRootFs, dirToKeep); |
| |
| // Clear out trash |
| int rc = -1; |
| try { |
| rc = shell.run(new String [] { "-expunge" } ); |
| } catch (Exception e) { |
| System.err.println("Exception raised from fs expunge " + |
| e.getLocalizedMessage()); |
| } |
| assertEquals(0, rc); |
| assertFalse("old checkpoint format not recognized", |
| trashRootFs.exists(dirToDelete)); |
| assertTrue("old checkpoint format directory should not be removed", |
| trashRootFs.exists(dirToKeep)); |
| } |
| |
| } |
| |
| public static void trashNonDefaultFS(Configuration conf) throws IOException { |
| conf.setLong(FS_TRASH_INTERVAL_KEY, 10); // 10 minute |
| // attempt non-default FileSystem trash |
| { |
| final FileSystem lfs = FileSystem.getLocal(conf); |
| Path p = TEST_DIR; |
| Path f = new Path(p, "foo/bar"); |
| if (lfs.exists(p)) { |
| lfs.delete(p, true); |
| } |
| try { |
| writeFile(lfs, f, 10); |
| FileSystem.closeAll(); |
| FileSystem localFs = FileSystem.get(URI.create("file:///"), conf); |
| Trash lTrash = new Trash(localFs, conf); |
| lTrash.moveToTrash(f.getParent()); |
| checkTrash(localFs, lTrash.getCurrentTrashDir(), f); |
| } finally { |
| if (lfs.exists(p)) { |
| lfs.delete(p, true); |
| } |
| } |
| } |
| } |
| |
| public void testTrash() throws IOException { |
| Configuration conf = new Configuration(); |
| conf.setClass("fs.file.impl", TestLFS.class, FileSystem.class); |
| trashShell(FileSystem.getLocal(conf), TEST_DIR); |
| } |
| |
| public void testNonDefaultFS() throws IOException { |
| Configuration conf = new Configuration(); |
| conf.setClass("fs.file.impl", TestLFS.class, FileSystem.class); |
| conf.set("fs.defaultFS", "invalid://host/bar/foo"); |
| trashNonDefaultFS(conf); |
| } |
| |
| public void testPluggableTrash() throws IOException { |
| Configuration conf = new Configuration(); |
| |
| // Test plugged TrashPolicy |
| conf.setClass("fs.trash.classname", TestTrashPolicy.class, TrashPolicy.class); |
| Trash trash = new Trash(conf); |
| assertTrue(trash.getTrashPolicy().getClass().equals(TestTrashPolicy.class)); |
| } |
| |
| @Test |
| public void testCheckpointInterval() throws IOException { |
| // Verify if fs.trash.checkpoint.interval is set to positive number |
| // but bigger than fs.trash.interval, |
| // the value should be reset to fs.trash.interval |
| verifyDefaultPolicyIntervalValues(10, 12, 10); |
| |
| // Verify if fs.trash.checkpoint.interval is set to positive number |
| // and smaller than fs.trash.interval, the value should be respected |
| verifyDefaultPolicyIntervalValues(10, 5, 5); |
| |
| // Verify if fs.trash.checkpoint.interval sets to 0 |
| // the value should be reset to fs.trash.interval |
| verifyDefaultPolicyIntervalValues(10, 0, 10); |
| |
| // Verify if fs.trash.checkpoint.interval sets to a negative number |
| // the value should be reset to fs.trash.interval |
| verifyDefaultPolicyIntervalValues(10, -1, 10); |
| } |
| |
| @Test |
| public void testMoveEmptyDirToTrash() throws Exception { |
| Configuration conf = new Configuration(); |
| conf.setClass(FS_FILE_IMPL_KEY, |
| RawLocalFileSystem.class, |
| FileSystem.class); |
| conf.setLong(FS_TRASH_INTERVAL_KEY, 1); // 1 min |
| FileSystem fs = FileSystem.get(conf); |
| verifyMoveEmptyDirToTrash(fs, conf); |
| } |
| |
| /** |
| * Simulate the carrier process of the trash emptier restarts, |
| * verify it honors the <b>fs.trash.interval</b> before and after restart. |
| * @throws Exception |
| */ |
| @Test |
| public void testTrashRestarts() throws Exception { |
| Configuration conf = new Configuration(); |
| conf.setClass("fs.trash.classname", |
| AuditableTrashPolicy.class, |
| TrashPolicy.class); |
| conf.setClass("fs.file.impl", TestLFS.class, FileSystem.class); |
| conf.set(FS_TRASH_INTERVAL_KEY, "50"); // in milliseconds for test |
| Trash trash = new Trash(conf); |
| // create 5 checkpoints |
| for(int i=0; i<5; i++) { |
| trash.checkpoint(); |
| } |
| |
| // Run the trash emptier for 120ms, it should run |
| // 2 times deletion as the interval is 50ms. |
| // Verify the checkpoints number when shutting down the emptier. |
| verifyAuditableTrashEmptier(trash, 120, 3); |
| |
| // reconfigure the interval to 100 ms |
| conf.set(FS_TRASH_INTERVAL_KEY, "100"); |
| Trash trashNew = new Trash(conf); |
| |
| // Run the trash emptier for 120ms, it should run |
| // 1 time deletion. |
| verifyAuditableTrashEmptier(trashNew, 120, 2); |
| } |
| |
| @Test |
| public void testTrashPermission() throws IOException { |
| Configuration conf = new Configuration(); |
| conf.setClass("fs.trash.classname", |
| TrashPolicyDefault.class, |
| TrashPolicy.class); |
| conf.setClass("fs.file.impl", TestLFS.class, FileSystem.class); |
| conf.set(FS_TRASH_INTERVAL_KEY, "0.2"); |
| verifyTrashPermission(FileSystem.getLocal(conf), conf); |
| } |
| |
| public void testTrashEmptier() throws Exception { |
| Configuration conf = new Configuration(); |
| // Trash with 12 second deletes and 6 seconds checkpoints |
| conf.set(FS_TRASH_INTERVAL_KEY, "0.2"); // 12 seconds |
| conf.setClass("fs.file.impl", TestLFS.class, FileSystem.class); |
| conf.set(FS_TRASH_CHECKPOINT_INTERVAL_KEY, "0.1"); // 6 seconds |
| FileSystem fs = FileSystem.getLocal(conf); |
| conf.set("fs.default.name", fs.getUri().toString()); |
| |
| Trash trash = new Trash(conf); |
| |
| // Start Emptier in background |
| Runnable emptier = trash.getEmptier(); |
| Thread emptierThread = new Thread(emptier); |
| emptierThread.start(); |
| |
| FsShell shell = new FsShell(); |
| shell.setConf(conf); |
| shell.init(); |
| // First create a new directory with mkdirs |
| Path myPath = new Path(TEST_DIR, "test/mkdirs"); |
| mkdir(fs, myPath); |
| int fileIndex = 0; |
| Set<String> checkpoints = new HashSet<String>(); |
| while (true) { |
| // Create a file with a new name |
| Path myFile = new Path(TEST_DIR, "test/mkdirs/myFile" + fileIndex++); |
| writeFile(fs, myFile, 10); |
| |
| // Delete the file to trash |
| String[] args = new String[2]; |
| args[0] = "-rm"; |
| args[1] = myFile.toString(); |
| int val = -1; |
| try { |
| val = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| } |
| assertTrue(val == 0); |
| |
| Path trashDir = shell.getCurrentTrashDir(); |
| FileStatus files[] = fs.listStatus(trashDir.getParent()); |
| // Scan files in .Trash and add them to set of checkpoints |
| for (FileStatus file : files) { |
| String fileName = file.getPath().getName(); |
| checkpoints.add(fileName); |
| } |
| // If checkpoints has 4 objects it is Current + 3 checkpoint directories |
| if (checkpoints.size() == 4) { |
| // The actual contents should be smaller since the last checkpoint |
| // should've been deleted and Current might not have been recreated yet |
| assertTrue(checkpoints.size() > files.length); |
| break; |
| } |
| Thread.sleep(5000); |
| } |
| emptierThread.interrupt(); |
| emptierThread.join(); |
| } |
| |
| /** |
| * @see TestCase#tearDown() |
| */ |
| @Override |
| protected void tearDown() throws IOException { |
| File trashDir = new File(TEST_DIR.toUri().getPath()); |
| if (trashDir.exists() && !FileUtil.fullyDelete(trashDir)) { |
| throw new IOException("Cannot remove data directory: " + trashDir); |
| } |
| } |
| |
| static class TestLFS extends LocalFileSystem { |
| Path home; |
| TestLFS() { |
| this(TEST_DIR); |
| } |
| TestLFS(final Path home) { |
| super(new RawLocalFileSystem() { |
| @Override |
| protected Path getInitialWorkingDirectory() { |
| return makeQualified(home); |
| } |
| |
| @Override |
| public Path getHomeDirectory() { |
| return makeQualified(home); |
| } |
| }); |
| this.home = home; |
| } |
| @Override |
| public Path getHomeDirectory() { |
| return home; |
| } |
| } |
| |
| /** |
| * test same file deletion - multiple time |
| * this is more of a performance test - shouldn't be run as a unit test |
| * @throws IOException |
| */ |
| public static void performanceTestDeleteSameFile() throws IOException{ |
| Path base = TEST_DIR; |
| Configuration conf = new Configuration(); |
| conf.setClass("fs.file.impl", TestLFS.class, FileSystem.class); |
| FileSystem fs = FileSystem.getLocal(conf); |
| |
| conf.set("fs.defaultFS", fs.getUri().toString()); |
| conf.setLong(FS_TRASH_INTERVAL_KEY, 10); //minutes.. |
| FsShell shell = new FsShell(); |
| shell.setConf(conf); |
| //Path trashRoot = null; |
| |
| Path myPath = new Path(base, "test/mkdirs"); |
| mkdir(fs, myPath); |
| |
| // create a file in that directory. |
| Path myFile; |
| long start; |
| long first = 0; |
| int retVal = 0; |
| int factor = 10; // how much slower any of subsequent deletion can be |
| myFile = new Path(base, "test/mkdirs/myFile"); |
| String [] args = new String[] {"-rm", myFile.toString()}; |
| int iters = 1000; |
| for(int i=0;i<iters; i++) { |
| |
| writeFile(fs, myFile, 10); |
| |
| start = Time.now(); |
| |
| try { |
| retVal = shell.run(args); |
| } catch (Exception e) { |
| System.err.println("Exception raised from Trash.run " + |
| e.getLocalizedMessage()); |
| throw new IOException(e.getMessage()); |
| } |
| |
| assertTrue(retVal == 0); |
| |
| long iterTime = Time.now() - start; |
| // take median of the first 10 runs |
| if(i<10) { |
| if(i==0) { |
| first = iterTime; |
| } |
| else { |
| first = (first + iterTime)/2; |
| } |
| } |
| // we don't want to print every iteration - let's do every 10th |
| int print_freq = iters/10; |
| |
| if(i>10) { |
| if((i%print_freq) == 0) |
| System.out.println("iteration="+i+";res =" + retVal + "; start=" + start |
| + "; iterTime = " + iterTime + " vs. firstTime=" + first); |
| long factoredTime = first*factor; |
| assertTrue(iterTime<factoredTime); //no more then twice of median first 10 |
| } |
| } |
| } |
| |
| public static void verifyMoveEmptyDirToTrash(FileSystem fs, |
| Configuration conf) throws IOException { |
| Path caseRoot = new Path( |
| GenericTestUtils.getTempPath("testUserTrash")); |
| Path testRoot = new Path(caseRoot, "trash-users"); |
| Path emptyDir = new Path(testRoot, "empty-dir"); |
| try (FileSystem fileSystem = fs){ |
| fileSystem.mkdirs(emptyDir); |
| Trash trash = new Trash(fileSystem, conf); |
| // Make sure trash root is clean |
| Path trashRoot = trash.getCurrentTrashDir(emptyDir); |
| fileSystem.delete(trashRoot, true); |
| // Move to trash should be succeed |
| assertTrue("Move an empty directory to trash failed", |
| trash.moveToTrash(emptyDir)); |
| // Verify the empty dir is removed |
| assertFalse("The empty directory still exists on file system", |
| fileSystem.exists(emptyDir)); |
| emptyDir = fileSystem.makeQualified(emptyDir); |
| Path dirInTrash = Path.mergePaths(trashRoot, emptyDir); |
| assertTrue("Directory wasn't moved to trash", |
| fileSystem.exists(dirInTrash)); |
| FileStatus[] flist = fileSystem.listStatus(dirInTrash); |
| assertTrue("Directory is not empty", |
| flist!= null && flist.length == 0); |
| } |
| } |
| |
| /** |
| * Create a bunch of files and set with different permission, after |
| * moved to trash, verify the location in trash directory is expected |
| * and the permission is reserved. |
| * |
| * @throws IOException |
| */ |
| public static void verifyTrashPermission(FileSystem fs, Configuration conf) |
| throws IOException { |
| Path caseRoot = new Path(BASE_PATH.getPath(), |
| "testTrashPermission"); |
| try (FileSystem fileSystem = fs){ |
| Trash trash = new Trash(fileSystem, conf); |
| FileSystemTestWrapper wrapper = |
| new FileSystemTestWrapper(fileSystem); |
| |
| short[] filePermssions = { |
| (short) 0600, |
| (short) 0644, |
| (short) 0660, |
| (short) 0700, |
| (short) 0750, |
| (short) 0755, |
| (short) 0775, |
| (short) 0777 |
| }; |
| |
| for(int i=0; i<filePermssions.length; i++) { |
| // Set different permission to files |
| FsPermission fsPermission = new FsPermission(filePermssions[i]); |
| Path file = new Path(caseRoot, "file" + i); |
| byte[] randomBytes = new byte[new Random().nextInt(10)]; |
| wrapper.writeFile(file, randomBytes); |
| wrapper.setPermission(file, fsPermission); |
| |
| // Move file to trash |
| trash.moveToTrash(file); |
| |
| // Verify the file is moved to trash, at expected location |
| Path trashDir = trash.getCurrentTrashDir(file); |
| if(!file.isAbsolute()) { |
| file = wrapper.makeQualified(file); |
| } |
| Path fileInTrash = Path.mergePaths(trashDir, file); |
| FileStatus fstat = wrapper.getFileStatus(fileInTrash); |
| assertTrue(String.format("File %s is not moved to trash", |
| fileInTrash.toString()), |
| wrapper.exists(fileInTrash)); |
| // Verify permission not change |
| assertTrue(String.format("Expected file: %s is %s, but actual is %s", |
| fileInTrash.toString(), |
| fsPermission.toString(), |
| fstat.getPermission().toString()), |
| fstat.getPermission().equals(fsPermission)); |
| } |
| |
| // Verify the trash directory can be removed |
| Path trashRoot = trash.getCurrentTrashDir(); |
| assertTrue(wrapper.delete(trashRoot, true)); |
| } |
| } |
| |
| private void verifyDefaultPolicyIntervalValues(long trashInterval, |
| long checkpointInterval, long expectedInterval) throws IOException { |
| Configuration conf = new Configuration(); |
| conf.setLong(FS_TRASH_INTERVAL_KEY, trashInterval); |
| conf.set("fs.trash.classname", TrashPolicyDefault.class.getName()); |
| conf.setLong(FS_TRASH_CHECKPOINT_INTERVAL_KEY, checkpointInterval); |
| Trash trash = new Trash(conf); |
| Emptier emptier = (Emptier)trash.getEmptier(); |
| assertEquals(expectedInterval, emptier.getEmptierInterval()); |
| } |
| |
| /** |
| * Launch the {@link Trash} emptier for given milliseconds, |
| * verify the number of checkpoints is expected. |
| */ |
| private void verifyAuditableTrashEmptier(Trash trash, |
| long timeAlive, |
| int expectedNumOfCheckpoints) |
| throws IOException { |
| Thread emptierThread = null; |
| try { |
| Runnable emptier = trash.getEmptier(); |
| emptierThread = new Thread(emptier); |
| emptierThread.start(); |
| |
| // Shutdown the emptier thread after a given time |
| Thread.sleep(timeAlive); |
| emptierThread.interrupt(); |
| emptierThread.join(); |
| |
| AuditableTrashPolicy at = (AuditableTrashPolicy) trash.getTrashPolicy(); |
| assertEquals( |
| String.format("Expected num of checkpoints is %s, but actual is %s", |
| expectedNumOfCheckpoints, at.getNumberOfCheckpoints()), |
| expectedNumOfCheckpoints, |
| at.getNumberOfCheckpoints()); |
| } catch (InterruptedException e) { |
| // Ignore |
| } finally { |
| // Avoid thread leak |
| if(emptierThread != null) { |
| emptierThread.interrupt(); |
| } |
| } |
| } |
| |
| // Test TrashPolicy. Don't care about implementation. |
| public static class TestTrashPolicy extends TrashPolicy { |
| public TestTrashPolicy() { } |
| |
| @Override |
| public void initialize(Configuration conf, FileSystem fs, Path home) { |
| } |
| |
| @Override |
| public void initialize(Configuration conf, FileSystem fs) { |
| } |
| |
| @Override |
| public boolean isEnabled() { |
| return false; |
| } |
| |
| @Override |
| public boolean moveToTrash(Path path) throws IOException { |
| return false; |
| } |
| |
| @Override |
| public void createCheckpoint() throws IOException { |
| } |
| |
| @Override |
| public void deleteCheckpoint() throws IOException { |
| } |
| |
| @Override |
| public Path getCurrentTrashDir() { |
| return null; |
| } |
| |
| @Override |
| public Path getCurrentTrashDir(Path path) throws IOException { |
| return null; |
| } |
| |
| @Override |
| public Runnable getEmptier() throws IOException { |
| return null; |
| } |
| } |
| |
| /** |
| * A fake {@link TrashPolicy} implementation, it keeps a count |
| * on number of checkpoints in the trash. It doesn't do anything |
| * other than updating the count. |
| * |
| */ |
| public static class AuditableTrashPolicy extends TrashPolicy { |
| |
| public AuditableTrashPolicy() {} |
| |
| public AuditableTrashPolicy(Configuration conf) |
| throws IOException { |
| this.initialize(conf, null); |
| } |
| |
| @Override |
| @Deprecated |
| public void initialize(Configuration conf, FileSystem fs, Path home) { |
| this.deletionInterval = (long)(conf.getFloat( |
| FS_TRASH_INTERVAL_KEY, FS_TRASH_INTERVAL_DEFAULT)); |
| } |
| |
| @Override |
| public void initialize(Configuration conf, FileSystem fs) { |
| this.deletionInterval = (long)(conf.getFloat( |
| FS_TRASH_INTERVAL_KEY, FS_TRASH_INTERVAL_DEFAULT)); |
| } |
| |
| @Override |
| public boolean moveToTrash(Path path) throws IOException { |
| return false; |
| } |
| |
| @Override |
| public void createCheckpoint() throws IOException { |
| AuditableCheckpoints.add(); |
| } |
| |
| @Override |
| public void deleteCheckpoint() throws IOException { |
| AuditableCheckpoints.delete(); |
| } |
| |
| @Override |
| public Path getCurrentTrashDir() { |
| return null; |
| } |
| |
| @Override |
| public Runnable getEmptier() throws IOException { |
| return new AuditableEmptier(getConf()); |
| } |
| |
| public int getNumberOfCheckpoints() { |
| return AuditableCheckpoints.get(); |
| } |
| |
| /** |
| * A fake emptier that simulates to delete a checkpoint |
| * in a fixed interval. |
| */ |
| private class AuditableEmptier implements Runnable { |
| private Configuration conf = null; |
| public AuditableEmptier(Configuration conf) { |
| this.conf = conf; |
| } |
| |
| @Override |
| public void run() { |
| AuditableTrashPolicy trash = null; |
| try { |
| trash = new AuditableTrashPolicy(conf); |
| } catch (IOException e1) {} |
| while(true) { |
| try { |
| Thread.sleep(deletionInterval); |
| trash.deleteCheckpoint(); |
| } catch (IOException e) { |
| // no exception |
| } catch (InterruptedException e) { |
| break; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean isEnabled() { |
| return true; |
| } |
| } |
| |
| /** |
| * Only counts the number of checkpoints, not do anything more. |
| * Declared as an inner static class to share state between |
| * testing threads. |
| */ |
| private static class AuditableCheckpoints { |
| |
| private static AtomicInteger numOfCheckpoint = |
| new AtomicInteger(0); |
| |
| private static void add() { |
| numOfCheckpoint.incrementAndGet(); |
| System.out.println(String |
| .format("Create a checkpoint, current number of checkpoints %d", |
| numOfCheckpoint.get())); |
| } |
| |
| private static void delete() { |
| if(numOfCheckpoint.get() > 0) { |
| numOfCheckpoint.decrementAndGet(); |
| System.out.println(String |
| .format("Delete a checkpoint, current number of checkpoints %d", |
| numOfCheckpoint.get())); |
| } |
| } |
| |
| private static int get() { |
| return numOfCheckpoint.get(); |
| } |
| } |
| } |