/**
* 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.server.datanode;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.DFSTestUtil;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.hdfs.protocol.Block;
import org.apache.hadoop.hdfs.server.datanode.FSDataset.FSVolume;
import org.apache.hadoop.hdfs.server.protocol.NamespaceInfo;
import org.apache.hadoop.io.IOUtils;
import junit.framework.TestCase;
/** Test if a datanode can correctly upgrade itself */
public class TestDatanodeRestart extends TestCase {
private FSDataset getFSDS(MiniDFSCluster cluster) {
DataNode dn = cluster.getDataNodes().get(0);
return (FSDataset) dn.data;
}
// test finalized replicas persist across DataNode restarts
public void testFinalizedReplicas() throws Exception {
// bring up a cluster of 3
Configuration conf = new Configuration();
conf.setLong("dfs.block.size", 1024L);
conf.setLong("dfs.block.crc.flush.interval", 100);
conf.setInt("dfs.write.packet.size", 512);
MiniDFSCluster cluster = new MiniDFSCluster(conf, 3, true, null);
cluster.waitActive();
FileSystem fs = cluster.getFileSystem();
try {
// test finalized replicas
final String TopDir = "/test";
DFSTestUtil util = new DFSTestUtil("TestCrcCorruption", 2, 3, 8 * 1024);
util.createFiles(fs, TopDir, (short) 3);
util.waitReplication(fs, TopDir, (short) 3);
util.checkFiles(fs, TopDir);
FSDataset data = getFSDS(cluster);
// save all the block CRC info;
NamespaceMap nm = data.volumeMap.getNamespaceMap(data.volumeMap.getNamespaceList()[0]);
Map<Block, Integer> bim = new HashMap<Block, Integer>();
for (int i = 0; i < nm.getNumBucket(); i++) {
for (Entry<Block, DatanodeBlockInfo> entry : nm.getBucket(i).blockInfoMap
.entrySet()) {
bim.put(entry.getKey(), entry.getValue().getBlockCrc());
}
}
// Wait another rounnd of block crc flushing happens
long lastFlush = Long.MAX_VALUE;
for (int i = 0; i < 100; i++) {
if (data.blockCrcMapFlusher.lastFlushed > 0) {
lastFlush = data.blockCrcMapFlusher.lastFlushed;
break;
} else if (i == 99) {
TestCase.fail("block CRC file is not flushed.");
} else {
Thread.sleep(100);
}
}
for (int i = 0; i < 100; i++) {
if (data.blockCrcMapFlusher.lastFlushed > lastFlush) {
break;
} else if (i == 99) {
TestCase.fail("block CRC file is not flushed.");
} else {
Thread.sleep(100);
}
}
cluster.restartDataNodes();
cluster.waitActive();
// Verify that block CRC is recovered.
data = getFSDS(cluster);
nm = data.volumeMap.getNamespaceMap(data.volumeMap.getNamespaceList()[0]);
for (int i = 0; i < nm.getNumBucket(); i++) {
for (Entry<Block, DatanodeBlockInfo> entry : nm.getBucket(i).blockInfoMap
.entrySet()) {
TestCase.assertEquals(bim.get(entry.getKey()).intValue(), entry
.getValue().getBlockCrc());
}
}
util.checkFiles(fs, TopDir);
} finally {
cluster.shutdown();
}
}
// test rbw replicas persist across DataNode restarts
public void testRbwReplicas() throws IOException {
Configuration conf = new Configuration();
conf.setLong("dfs.block.size", 1024L);
conf.setInt("dfs.write.packet.size", 512);
conf.setBoolean("dfs.support.append", true);
MiniDFSCluster cluster = new MiniDFSCluster(conf, 1, true, null);
cluster.waitActive();
try {
testRbwReplicas(cluster, false);
testRbwReplicas(cluster, true);
} finally {
cluster.shutdown();
}
}
private void testRbwReplicas(MiniDFSCluster cluster, boolean isCorrupt)
throws IOException {
FSDataOutputStream out = null;
try {
FileSystem fs = cluster.getFileSystem();
NamespaceInfo nsInfo = cluster.getNameNode().versionRequest();
final int fileLen = 515;
// create some rbw replicas on disk
byte[] writeBuf = new byte[fileLen];
new Random().nextBytes(writeBuf);
final Path src = new Path("/test.txt");
out = fs.create(src);
out.write(writeBuf);
out.sync();
DataNode dn = cluster.getDataNodes().get(0);
// corrupt rbw replicas
for (FSVolume volume : ((FSDataset) dn.data).volumes.getVolumes()) {
File rbwDir = volume.getRbwDir(nsInfo.getNamespaceID());
for (File file : rbwDir.listFiles()) {
if (isCorrupt) {
if (Block.isSeparateChecksumBlockFilename(file.getName())) {
new RandomAccessFile(file, "rw").setLength(fileLen - 1); // corrupt
} else if (Block.isInlineChecksumBlockFilename(file.getName())) {
new RandomAccessFile(file, "rw").setLength(file.length() - 1); // corrupt
}
}
}
}
cluster.restartDataNodes();
cluster.waitActive();
dn = cluster.getDataNodes().get(0);
// check volumeMap: one rbw replica
NamespaceMap volumeMap = ((FSDataset) (dn.data)).volumeMap
.getNamespaceMap(nsInfo.getNamespaceID());
assertEquals(1, volumeMap.size());
Block replica = null;
for (int i = 0; i < volumeMap.getNumBucket(); i++) {
Set<Block> blockSet = volumeMap.getBucket(i).blockInfoMap.keySet();
if (blockSet.isEmpty()) {
continue;
}
Block r = blockSet.iterator().next();
if (r != null) {
replica = r;
break;
}
}
if (isCorrupt) {
assertEquals((fileLen - 1), replica.getNumBytes());
} else {
assertEquals(fileLen, replica.getNumBytes());
}
dn.data.invalidate(nsInfo.getNamespaceID(), new Block[] { replica });
fs.delete(src, false);
} finally {
IOUtils.closeStream(out);
}
}
}