blob: 046b02594370fe824fe096539214269fa5e14fce [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.hdfs;
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.hdfs.server.common.HdfsConstants.NodeType;
import org.apache.hadoop.hdfs.server.common.HdfsConstants.StartupOption;
import static org.apache.hadoop.hdfs.server.common.HdfsConstants.NodeType.NAME_NODE;
import static org.apache.hadoop.hdfs.server.common.HdfsConstants.NodeType.DATA_NODE;
/**
* This test ensures the appropriate response (successful or failure) from
* the system when the system is started under various storage state and
* version conditions.
*/
public class TestDFSStorageStateRecovery extends TestCase {
private static final Log LOG = LogFactory.getLog(
"org.apache.hadoop.hdfs.TestDFSStorageStateRecovery");
private Configuration conf = null;
private int testCounter = 0;
private MiniDFSCluster cluster = null;
/**
* The test case table. Each row represents a test case. This table is
* taken from the table in Apendix A of the HDFS Upgrade Test Plan
* (TestPlan-HdfsUpgrade.html) attached to
* http://issues.apache.org/jira/browse/HADOOP-702
* The column meanings are:
* 0) current directory exists
* 1) previous directory exists
* 2) previous.tmp directory exists
* 3) removed.tmp directory exists
* 4) lastcheckpoint.tmp directory exists
* 5) node should recover and startup
* 6) current directory should exist after recovery but before startup
* 7) previous directory should exist after recovery but before startup
*/
static boolean[][] testCases = new boolean[][] {
new boolean[] {true, false, false, false, false, true, true, false}, // 1
new boolean[] {true, true, false, false, false, true, true, true }, // 2
new boolean[] {true, false, true, false, false, true, true, true }, // 3
new boolean[] {true, true, true, true, false, false, false, false}, // 4
new boolean[] {true, true, true, false, false, false, false, false}, // 4
new boolean[] {false, true, true, true, false, false, false, false}, // 4
new boolean[] {false, true, true, false, false, false, false, false}, // 4
new boolean[] {false, false, false, false, false, false, false, false}, // 5
new boolean[] {false, true, false, false, false, false, false, false}, // 6
new boolean[] {false, false, true, false, false, true, true, false}, // 7
new boolean[] {true, false, false, true, false, true, true, false}, // 8
new boolean[] {true, true, false, true, false, false, false, false}, // 9
new boolean[] {true, true, true, true, false, false, false, false}, // 10
new boolean[] {true, false, true, true, false, false, false, false}, // 10
new boolean[] {false, true, true, true, false, false, false, false}, // 10
new boolean[] {false, false, true, true, false, false, false, false}, // 10
new boolean[] {false, false, false, true, false, false, false, false}, // 11
new boolean[] {false, true, false, true, false, true, true, true }, // 12
// name-node specific cases
new boolean[] {true, false, false, false, true, true, true, false}, // 13
new boolean[] {true, true, false, false, true, true, true, false}, // 13
new boolean[] {false, false, false, false, true, true, true, false}, // 14
new boolean[] {false, true, false, false, true, true, true, false}, // 14
new boolean[] {true, false, true, false, true, false, false, false}, // 15
new boolean[] {true, true, false, true, true, false, false, false} // 16
};
private static final int NUM_NN_TEST_CASES = testCases.length;
private static final int NUM_DN_TEST_CASES = 18;
/**
* Writes an INFO log message containing the parameters. Only
* the first 4 elements of the state array are included in the message.
*/
void log(String label, int numDirs, int testCaseNum, boolean[] state) {
LOG.info("============================================================");
LOG.info("***TEST " + (testCounter++) + "*** "
+ label + ":"
+ " numDirs="+numDirs
+ " testCase="+testCaseNum
+ " current="+state[0]
+ " previous="+state[1]
+ " previous.tmp="+state[2]
+ " removed.tmp="+state[3]
+ " lastcheckpoint.tmp="+state[4]);
}
/**
* Sets up the storage directories for the given node type, either
* dfs.name.dir or dfs.data.dir. For each element in dfs.name.dir or
* dfs.data.dir, the subdirectories represented by the first four elements
* of the <code>state</code> array will be created and populated.
* See UpgradeUtilities.createStorageDirs().
*
* @param nodeType
* the type of node that storage should be created for. Based on this
* parameter either dfs.name.dir or dfs.data.dir is used from the global conf.
* @param state
* a row from the testCases table which indicates which directories
* to setup for the node
* @return file paths representing either dfs.name.dir or dfs.data.dir
* directories
*/
String[] createStorageState(NodeType nodeType, boolean[] state) throws Exception {
String[] baseDirs = (nodeType == NAME_NODE ?
conf.getStrings(DFSConfigKeys.DFS_NAMENODE_NAME_DIR_KEY) :
conf.getStrings(DFSConfigKeys.DFS_DATANODE_DATA_DIR_KEY));
UpgradeUtilities.createEmptyDirs(baseDirs);
if (state[0]) // current
UpgradeUtilities.createStorageDirs(nodeType, baseDirs, "current");
if (state[1]) // previous
UpgradeUtilities.createStorageDirs(nodeType, baseDirs, "previous");
if (state[2]) // previous.tmp
UpgradeUtilities.createStorageDirs(nodeType, baseDirs, "previous.tmp");
if (state[3]) // removed.tmp
UpgradeUtilities.createStorageDirs(nodeType, baseDirs, "removed.tmp");
if (state[4]) // lastcheckpoint.tmp
UpgradeUtilities.createStorageDirs(nodeType, baseDirs, "lastcheckpoint.tmp");
return baseDirs;
}
/**
* Verify that the current and/or previous exist as indicated by
* the method parameters. If previous exists, verify that
* it hasn't been modified by comparing the checksum of all it's
* containing files with their original checksum. It is assumed that
* the server has recovered.
*/
void checkResult(NodeType nodeType, String[] baseDirs,
boolean currentShouldExist, boolean previousShouldExist)
throws IOException
{
switch (nodeType) {
case NAME_NODE:
if (currentShouldExist) {
for (int i = 0; i < baseDirs.length; i++) {
assertTrue(new File(baseDirs[i],"current").isDirectory());
assertTrue(new File(baseDirs[i],"current/VERSION").isFile());
assertTrue(new File(baseDirs[i],"current/edits").isFile());
assertTrue(new File(baseDirs[i],"current/fsimage").isFile());
assertTrue(new File(baseDirs[i],"current/fstime").isFile());
}
}
break;
case DATA_NODE:
if (currentShouldExist) {
for (int i = 0; i < baseDirs.length; i++) {
assertEquals(
UpgradeUtilities.checksumContents(
nodeType, new File(baseDirs[i],"current")),
UpgradeUtilities.checksumMasterContents(nodeType));
}
}
break;
}
if (previousShouldExist) {
for (int i = 0; i < baseDirs.length; i++) {
assertTrue(new File(baseDirs[i],"previous").isDirectory());
assertEquals(
UpgradeUtilities.checksumContents(
nodeType, new File(baseDirs[i],"previous")),
UpgradeUtilities.checksumMasterContents(nodeType));
}
}
}
/**
* This test iterates over the testCases table and attempts
* to startup the NameNode normally.
*/
public void testNNStorageStates() throws Exception {
String[] baseDirs;
for (int numDirs = 1; numDirs <= 2; numDirs++) {
conf = new HdfsConfiguration();
conf.setInt("dfs.datanode.scan.period.hours", -1);
conf = UpgradeUtilities.initializeStorageStateConf(numDirs, conf);
for (int i = 0; i < NUM_NN_TEST_CASES; i++) {
boolean[] testCase = testCases[i];
boolean shouldRecover = testCase[5];
boolean curAfterRecover = testCase[6];
boolean prevAfterRecover = testCase[7];
log("NAME_NODE recovery", numDirs, i, testCase);
baseDirs = createStorageState(NAME_NODE, testCase);
if (shouldRecover) {
cluster = new MiniDFSCluster(conf, 0, StartupOption.REGULAR);
checkResult(NAME_NODE, baseDirs, curAfterRecover, prevAfterRecover);
cluster.shutdown();
} else {
try {
cluster = new MiniDFSCluster(conf, 0, StartupOption.REGULAR);
throw new AssertionError("NameNode should have failed to start");
} catch (IOException expected) {
// the exception is expected
// check that the message says "not formatted"
// when storage directory is empty (case #5)
if(!testCases[i][0] && !testCases[i][2]
&& !testCases[i][1] && !testCases[i][3] && !testCases[i][4]) {
assertTrue(expected.getLocalizedMessage().contains(
"NameNode is not formatted"));
}
}
}
cluster.shutdown();
} // end testCases loop
} // end numDirs loop
}
/**
* This test iterates over the testCases table and attempts
* to startup the DataNode normally.
*/
public void testDNStorageStates() throws Exception {
String[] baseDirs;
for (int numDirs = 1; numDirs <= 2; numDirs++) {
conf = new HdfsConfiguration();
conf.setInt("dfs.datanode.scan.period.hours", -1);
conf = UpgradeUtilities.initializeStorageStateConf(numDirs, conf);
for (int i = 0; i < NUM_DN_TEST_CASES; i++) {
boolean[] testCase = testCases[i];
boolean shouldRecover = testCase[5];
boolean curAfterRecover = testCase[6];
boolean prevAfterRecover = testCase[7];
log("DATA_NODE recovery", numDirs, i, testCase);
createStorageState(NAME_NODE,
new boolean[] {true, true, false, false, false});
cluster = new MiniDFSCluster(conf, 0, StartupOption.REGULAR);
baseDirs = createStorageState(DATA_NODE, testCase);
if (!testCase[0] && !testCase[1] && !testCase[2] && !testCase[3]) {
// DataNode will create and format current if no directories exist
cluster.startDataNodes(conf, 1, false, StartupOption.REGULAR, null);
} else {
if (shouldRecover) {
cluster.startDataNodes(conf, 1, false, StartupOption.REGULAR, null);
checkResult(DATA_NODE, baseDirs, curAfterRecover, prevAfterRecover);
} else {
try {
cluster.startDataNodes(conf, 1, false, StartupOption.REGULAR, null);
throw new AssertionError("DataNode should have failed to start");
} catch (Exception expected) {
// expected
}
}
}
cluster.shutdown();
} // end testCases loop
} // end numDirs loop
}
protected void setUp() throws Exception {
LOG.info("Setting up the directory structures.");
UpgradeUtilities.initialize();
}
protected void tearDown() throws Exception {
LOG.info("Shutting down MiniDFSCluster");
if (cluster != null) cluster.shutdown();
}
public static void main(String[] args) throws Exception {
TestDFSStorageStateRecovery test = new TestDFSStorageStateRecovery();
test.testNNStorageStates();
test.testDNStorageStates();
}
}