blob: 87ebd7363aebc9d09ddc154c29606cb8db125059 [file] [log] [blame]
/**
* 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.mapreduce.util;
import java.io.File;
import java.io.IOException;
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.mapreduce.util.MRAsyncDiskService;
import org.junit.Test;
/**
* A test for MRAsyncDiskService.
*/
public class TestMRAsyncDiskService extends TestCase {
public static final Log LOG = LogFactory.getLog(TestMRAsyncDiskService.class);
private static String TEST_ROOT_DIR = new Path(System.getProperty(
"test.build.data", "/tmp")).toString();
@Override
protected void setUp() throws Exception {
FileUtil.fullyDelete(new File(TEST_ROOT_DIR));
}
/**
* Given 'pathname', compute an equivalent path relative to the cwd.
* @param pathname the path to a directory.
* @return the path to that same directory, relative to ${user.dir}.
*/
private String relativeToWorking(String pathname) {
String cwd = System.getProperty("user.dir", "/");
// normalize pathname and cwd into full directory paths.
pathname = (new Path(pathname)).toUri().getPath();
cwd = (new Path(cwd)).toUri().getPath();
String [] cwdParts = cwd.split(File.separator);
String [] pathParts = pathname.split(File.separator);
// There are three possible cases:
// 1) pathname and cwd are equal. Return '.'
// 2) pathname is under cwd. Return the components that are under it.
// e.g., cwd = /a/b, path = /a/b/c, return 'c'
// 3) pathname is outside of cwd. Find the common components, if any,
// and subtract them from the returned path, then return enough '..'
// components to "undo" the non-common components of cwd, then all
// the remaining parts of pathname.
// e.g., cwd = /a/b, path = /a/c, return '../c'
if (cwd.equals(pathname)) {
LOG.info("relative to working: " + pathname + " -> .");
return "."; // They match exactly.
}
// Determine how many path components are in common between cwd and path.
int common = 0;
for (int i = 0; i < Math.min(cwdParts.length, pathParts.length); i++) {
if (cwdParts[i].equals(pathParts[i])) {
common++;
} else {
break;
}
}
// output path stringbuilder.
StringBuilder sb = new StringBuilder();
// For everything in cwd that isn't in pathname, add a '..' to undo it.
int parentDirsRequired = cwdParts.length - common;
for (int i = 0; i < parentDirsRequired; i++) {
sb.append("..");
sb.append(File.separator);
}
// Then append all non-common parts of 'pathname' itself.
for (int i = common; i < pathParts.length; i++) {
sb.append(pathParts[i]);
sb.append(File.separator);
}
// Don't end with a '/'.
String s = sb.toString();
if (s.endsWith(File.separator)) {
s = s.substring(0, s.length() - 1);
}
LOG.info("relative to working: " + pathname + " -> " + s);
return s;
}
@Test
/** Test that the relativeToWorking() method above does what we expect. */
public void testRelativeToWorking() {
assertEquals(".", relativeToWorking(System.getProperty("user.dir", ".")));
String cwd = System.getProperty("user.dir", ".");
Path cwdPath = new Path(cwd);
Path subdir = new Path(cwdPath, "foo");
assertEquals("foo", relativeToWorking(subdir.toUri().getPath()));
Path subsubdir = new Path(subdir, "bar");
assertEquals("foo/bar", relativeToWorking(subsubdir.toUri().getPath()));
Path parent = new Path(cwdPath, "..");
assertEquals("..", relativeToWorking(parent.toUri().getPath()));
Path sideways = new Path(parent, "baz");
assertEquals("../baz", relativeToWorking(sideways.toUri().getPath()));
}
@Test
/** Test that volumes specified as relative paths are handled properly
* by MRAsyncDiskService (MAPREDUCE-1887).
*/
public void testVolumeNormalization() throws Throwable {
LOG.info("TEST_ROOT_DIR is " + TEST_ROOT_DIR);
String relativeTestRoot = relativeToWorking(TEST_ROOT_DIR);
FileSystem localFileSystem = FileSystem.getLocal(new Configuration());
String [] vols = new String[] { relativeTestRoot + "/0",
relativeTestRoot + "/1" };
// Put a file in one of the volumes to be cleared on startup.
Path delDir = new Path(vols[0], MRAsyncDiskService.TOBEDELETED);
localFileSystem.mkdirs(delDir);
localFileSystem.create(new Path(delDir, "foo")).close();
MRAsyncDiskService service = new MRAsyncDiskService(
localFileSystem, vols);
makeSureCleanedUp(vols, service);
}
/**
* This test creates some directories and then removes them through
* MRAsyncDiskService.
*/
@Test
public void testMRAsyncDiskService() throws Throwable {
FileSystem localFileSystem = FileSystem.getLocal(new Configuration());
String[] vols = new String[]{TEST_ROOT_DIR + "/0",
TEST_ROOT_DIR + "/1"};
MRAsyncDiskService service = new MRAsyncDiskService(
localFileSystem, vols);
String a = "a";
String b = "b";
String c = "b/c";
String d = "d";
File fa = new File(vols[0], a);
File fb = new File(vols[1], b);
File fc = new File(vols[1], c);
File fd = new File(vols[1], d);
// Create the directories
fa.mkdirs();
fb.mkdirs();
fc.mkdirs();
fd.mkdirs();
assertTrue(fa.exists());
assertTrue(fb.exists());
assertTrue(fc.exists());
assertTrue(fd.exists());
// Move and delete them
service.moveAndDeleteRelativePath(vols[0], a);
assertFalse(fa.exists());
service.moveAndDeleteRelativePath(vols[1], b);
assertFalse(fb.exists());
assertFalse(fc.exists());
assertFalse(service.moveAndDeleteRelativePath(vols[1], "not_exists"));
// asyncDiskService is NOT able to delete files outside all volumes.
IOException ee = null;
try {
service.moveAndDeleteAbsolutePath(TEST_ROOT_DIR + "/2");
} catch (IOException e) {
ee = e;
}
assertNotNull("asyncDiskService should not be able to delete files "
+ "outside all volumes", ee);
// asyncDiskService is able to automatically find the file in one
// of the volumes.
assertTrue(service.moveAndDeleteAbsolutePath(vols[1] + Path.SEPARATOR_CHAR + d));
// Make sure everything is cleaned up
makeSureCleanedUp(vols, service);
}
/**
* This test creates some directories inside the volume roots, and then
* call asyncDiskService.MoveAndDeleteAllVolumes.
* We should be able to delete all files/dirs inside the volumes except
* the toBeDeleted directory.
*/
@Test
public void testMRAsyncDiskServiceMoveAndDeleteAllVolumes() throws Throwable {
FileSystem localFileSystem = FileSystem.getLocal(new Configuration());
String[] vols = new String[]{TEST_ROOT_DIR + "/0",
TEST_ROOT_DIR + "/1"};
MRAsyncDiskService service = new MRAsyncDiskService(
localFileSystem, vols);
String a = "a";
String b = "b";
String c = "b/c";
String d = "d";
File fa = new File(vols[0], a);
File fb = new File(vols[1], b);
File fc = new File(vols[1], c);
File fd = new File(vols[1], d);
// Create the directories
fa.mkdirs();
fb.mkdirs();
fc.mkdirs();
fd.mkdirs();
assertTrue(fa.exists());
assertTrue(fb.exists());
assertTrue(fc.exists());
assertTrue(fd.exists());
// Delete all of them
service.cleanupAllVolumes();
assertFalse(fa.exists());
assertFalse(fb.exists());
assertFalse(fc.exists());
assertFalse(fd.exists());
// Make sure everything is cleaned up
makeSureCleanedUp(vols, service);
}
/**
* This test creates some directories inside the toBeDeleted directory and
* then start the asyncDiskService.
* AsyncDiskService will create tasks to delete the content inside the
* toBeDeleted directories.
*/
@Test
public void testMRAsyncDiskServiceStartupCleaning() throws Throwable {
FileSystem localFileSystem = FileSystem.getLocal(new Configuration());
String[] vols = new String[]{TEST_ROOT_DIR + "/0",
TEST_ROOT_DIR + "/1"};
String a = "a";
String b = "b";
String c = "b/c";
String d = "d";
// Create directories inside SUBDIR
String suffix = Path.SEPARATOR_CHAR + MRAsyncDiskService.TOBEDELETED;
File fa = new File(vols[0] + suffix, a);
File fb = new File(vols[1] + suffix, b);
File fc = new File(vols[1] + suffix, c);
File fd = new File(vols[1] + suffix, d);
// Create the directories
fa.mkdirs();
fb.mkdirs();
fc.mkdirs();
fd.mkdirs();
assertTrue(fa.exists());
assertTrue(fb.exists());
assertTrue(fc.exists());
assertTrue(fd.exists());
// Create the asyncDiskService which will delete all contents inside SUBDIR
MRAsyncDiskService service = new MRAsyncDiskService(
localFileSystem, vols);
// Make sure everything is cleaned up
makeSureCleanedUp(vols, service);
}
private void makeSureCleanedUp(String[] vols, MRAsyncDiskService service)
throws Throwable {
// Sleep at most 5 seconds to make sure the deleted items are all gone.
service.shutdown();
if (!service.awaitTermination(5000)) {
fail("MRAsyncDiskService is still not shutdown in 5 seconds!");
}
// All contents should be gone by now.
for (int i = 0; i < vols.length; i++) {
File subDir = new File(vols[0]);
String[] subDirContent = subDir.list();
assertEquals("Volume should contain a single child: "
+ MRAsyncDiskService.TOBEDELETED, 1, subDirContent.length);
File toBeDeletedDir = new File(vols[0], MRAsyncDiskService.TOBEDELETED);
String[] content = toBeDeletedDir.list();
assertNotNull("Cannot find " + toBeDeletedDir, content);
assertEquals("" + toBeDeletedDir + " should be empty now.", 0,
content.length);
}
}
@Test
public void testToleratesSomeUnwritableVolumes() throws Throwable {
FileSystem localFileSystem = FileSystem.getLocal(new Configuration());
String[] vols = new String[]{TEST_ROOT_DIR + "/0",
TEST_ROOT_DIR + "/1"};
assertTrue(new File(vols[0]).mkdirs());
assertEquals(0, FileUtil.chmod(vols[0], "400")); // read only
try {
new MRAsyncDiskService(localFileSystem, vols);
} finally {
FileUtil.chmod(vols[0], "755"); // make writable again
}
}
}