/** * 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.namenode; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.util.Random; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.FileUtil; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.hdfs.server.common.Storage; import org.apache.hadoop.hdfs.server.namenode.FSImage.NameNodeFile; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * Test restoring failed storage directories on checkpoint. */ public class TestStorageRestore { public static final String NAME_NODE_HOST = "localhost:"; public static final String NAME_NODE_HTTP_HOST = "0.0.0.0:"; private Configuration config; private File hdfsDir=null; static final long seed = 0xAAAAEEFL; static final int blockSize = 4096; static final int fileSize = 8192; private File path1, path2, path3; private MiniDFSCluster cluster; private void writeFile(FileSystem fileSys, Path name, int repl) throws IOException { FSDataOutputStream stm = fileSys.create(name, true, fileSys.getConf().getInt("io.file.buffer.size", 4096), (short)repl, (long)blockSize); byte[] buffer = new byte[fileSize]; Random rand = new Random(seed); rand.nextBytes(buffer); stm.write(buffer); stm.close(); } @Before public void setUpNameDirs() throws Exception { config = new Configuration(); String baseDir = System.getProperty("test.build.data", "/tmp"); hdfsDir = new File(baseDir, "dfs"); if (hdfsDir.exists()) { FileUtil.fullyDelete(hdfsDir); } hdfsDir.mkdir(); path1 = new File(hdfsDir, "name1"); path2 = new File(hdfsDir, "name2"); path3 = new File(hdfsDir, "name3"); path1.mkdir(); path2.mkdir(); path3.mkdir(); String nameDir = new String(path1.getPath() + "," + path2.getPath()); config.set("dfs.name.dir", nameDir); config.set("dfs.name.edits.dir", nameDir + "," + path3.getPath()); config.set("fs.checkpoint.dir",new File(hdfsDir, "secondary").getPath()); FileSystem.setDefaultUri(config, "hdfs://"+NAME_NODE_HOST + "0"); config.set("dfs.secondary.http.address", "0.0.0.0:0"); config.setBoolean("dfs.name.dir.restore", true); } @After public void cleanUpNameDirs() throws Exception { if (hdfsDir.exists()) { FileUtil.fullyDelete(hdfsDir); } } /** * Remove edits and storage directories. */ public void invalidateStorage(FSImage fi) throws IOException { fi.getEditLog().removeEditsAndStorageDir(2); // name3 fi.getEditLog().removeEditsAndStorageDir(1); // name2 } /** * Check the lengths of the image and edits files. */ public void checkFiles(boolean expectValid) { final String imgName = Storage.STORAGE_DIR_CURRENT + "/" + NameNodeFile.IMAGE.getName(); final String editsName = Storage.STORAGE_DIR_CURRENT + "/" + NameNodeFile.EDITS.getName(); File fsImg1 = new File(path1, imgName); File fsImg2 = new File(path2, imgName); File fsImg3 = new File(path3, imgName); File fsEdits1 = new File(path1, editsName); File fsEdits2 = new File(path2, editsName); File fsEdits3 = new File(path3, editsName); if (expectValid) { assertTrue(fsImg1.length() == fsImg2.length()); assertTrue(0 == fsImg3.length()); // Shouldn't be created assertTrue(fsEdits1.length() == fsEdits2.length()); assertTrue(fsEdits1.length() == fsEdits3.length()); } else { assertTrue(fsEdits1.length() != fsEdits2.length()); assertTrue(fsEdits1.length() != fsEdits3.length()); } } /** * test * 1. create DFS cluster with 3 storage directories - 2 EDITS_IMAGE, 1 EDITS * 2. create a cluster and write a file * 3. corrupt/disable one storage (or two) by removing * 4. run doCheckpoint - it will fail on removed dirs (which * will invalidate the storages) * 5. write another file * 6. check that edits and fsimage differ * 7. run doCheckpoint * 8. verify that all the image and edits files are the same. */ @Test public void testStorageRestore() throws Exception { int numDatanodes = 2; cluster = new MiniDFSCluster(0, config, numDatanodes, true, false, true, null, null, null, null); cluster.waitActive(); SecondaryNameNode secondary = new SecondaryNameNode(config); FileSystem fs = cluster.getFileSystem(); Path path = new Path("/", "test"); writeFile(fs, path, 2); invalidateStorage(cluster.getNameNode().getFSImage()); path = new Path("/", "test1"); writeFile(fs, path, 2); checkFiles(false); secondary.doCheckpoint(); checkFiles(true); secondary.shutdown(); cluster.shutdown(); } /** * Test to simulate interleaved checkpointing by 2 2NNs after a storage * directory has been taken offline. The first will cause the directory to * come back online, but it won't have any valid contents. The second 2NN will * then try to perform a checkpoint. The NN should not serve up the image or * edits from the restored (empty) dir. */ @Test public void testCheckpointWithRestoredDirectory() throws IOException { SecondaryNameNode secondary = null; try { cluster = new MiniDFSCluster(0, config, 1, true, false, true, null, null, null, null); cluster.waitActive(); secondary = new SecondaryNameNode(config); FSImage fsImage = cluster.getNameNode().getFSImage(); FileSystem fs = cluster.getFileSystem(); Path path1 = new Path("/", "test"); writeFile(fs, path1, 2); // Take name3 offline fsImage.getEditLog().removeEditsAndStorageDir(2); // Simulate a 2NN beginning a checkpoint, but not finishing. This will // cause name3 to be restored. cluster.getNameNode().rollEditLog(); // Now another 2NN comes along to do a full checkpoint. secondary.doCheckpoint(); // The created file should still exist in the in-memory FS state after the // checkpoint. assertTrue("File missing after checkpoint", fs.exists(path1)); secondary.shutdown(); // Restart the NN so it reloads the edits from on-disk. cluster.restartNameNode(); // The created file should still exist after the restart. assertTrue("path should still exist after restart", fs.exists(path1)); } finally { if (cluster != null) { cluster.shutdown(); } if (secondary != null) { secondary.shutdown(); } } } }