| /** |
| * 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.ha; |
| |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.permission.FsPermission; |
| import org.apache.hadoop.hdfs.HAUtil; |
| import org.apache.hadoop.hdfs.MiniDFSCluster; |
| import org.apache.hadoop.hdfs.MiniDFSNNTopology; |
| import org.apache.hadoop.hdfs.server.namenode.EditLogFileOutputStream; |
| import org.apache.hadoop.hdfs.server.namenode.FSImageTestUtil; |
| import org.apache.hadoop.hdfs.server.namenode.NNStorage; |
| import org.apache.hadoop.hdfs.server.namenode.NameNodeAdapter; |
| import org.apache.hadoop.io.IOUtils; |
| import org.apache.hadoop.test.GenericTestUtils; |
| import org.junit.Test; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.collect.Lists; |
| |
| /** |
| * Test cases for the handling of edit logs during failover |
| * and startup of the standby node. |
| */ |
| public class TestEditLogsDuringFailover { |
| private static final Log LOG = |
| LogFactory.getLog(TestEditLogsDuringFailover.class); |
| private static final int NUM_DIRS_IN_LOG = 5; |
| |
| static { |
| // No need to fsync for the purposes of tests. This makes |
| // the tests run much faster. |
| EditLogFileOutputStream.setShouldSkipFsyncForTesting(true); |
| } |
| |
| @Test |
| public void testStartup() throws Exception { |
| Configuration conf = new Configuration(); |
| HAUtil.setAllowStandbyReads(conf, true); |
| MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf) |
| .nnTopology(MiniDFSNNTopology.simpleHATopology()) |
| .numDataNodes(0) |
| .build(); |
| try { |
| // During HA startup, both nodes should be in |
| // standby and we shouldn't have any edits files |
| // in any edits directory! |
| List<URI> allDirs = Lists.newArrayList(); |
| allDirs.addAll(cluster.getNameDirs(0)); |
| allDirs.addAll(cluster.getNameDirs(1)); |
| allDirs.add(cluster.getSharedEditsDir(0, 1)); |
| assertNoEditFiles(allDirs); |
| |
| // Set the first NN to active, make sure it creates edits |
| // in its own dirs and the shared dir. The standby |
| // should still have no edits! |
| cluster.transitionToActive(0); |
| |
| assertEditFiles(cluster.getNameDirs(0), |
| NNStorage.getInProgressEditsFileName(1)); |
| assertEditFiles( |
| Collections.singletonList(cluster.getSharedEditsDir(0, 1)), |
| NNStorage.getInProgressEditsFileName(1)); |
| assertNoEditFiles(cluster.getNameDirs(1)); |
| |
| cluster.getNameNode(0).getRpcServer().mkdirs("/test", |
| FsPermission.createImmutable((short)0755), true); |
| |
| // Restarting the standby should not finalize any edits files |
| // in the shared directory when it starts up! |
| cluster.restartNameNode(1); |
| |
| assertEditFiles(cluster.getNameDirs(0), |
| NNStorage.getInProgressEditsFileName(1)); |
| assertEditFiles( |
| Collections.singletonList(cluster.getSharedEditsDir(0, 1)), |
| NNStorage.getInProgressEditsFileName(1)); |
| assertNoEditFiles(cluster.getNameDirs(1)); |
| |
| // Additionally it should not have applied any in-progress logs |
| // at start-up -- otherwise, it would have read half-way into |
| // the current log segment, and on the next roll, it would have to |
| // either replay starting in the middle of the segment (not allowed) |
| // or double-replay the edits (incorrect). |
| assertNull(NameNodeAdapter.getFileInfo(cluster.getNameNode(1), "/test", true)); |
| |
| cluster.getNameNode(0).getRpcServer().mkdirs("/test2", |
| FsPermission.createImmutable((short)0755), true); |
| |
| // If we restart NN0, it'll come back as standby, and we can |
| // transition NN1 to active and make sure it reads edits correctly at this point. |
| cluster.restartNameNode(0); |
| cluster.transitionToActive(1); |
| |
| // NN1 should have both the edits that came before its restart, and the edits that |
| // came after its restart. |
| assertNotNull(NameNodeAdapter.getFileInfo(cluster.getNameNode(1), "/test", true)); |
| assertNotNull(NameNodeAdapter.getFileInfo(cluster.getNameNode(1), "/test2", true)); |
| } finally { |
| cluster.shutdown(); |
| } |
| } |
| |
| private void testFailoverFinalizesAndReadsInProgress( |
| boolean partialTxAtEnd) throws Exception { |
| Configuration conf = new Configuration(); |
| MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf) |
| .nnTopology(MiniDFSNNTopology.simpleHATopology()) |
| .numDataNodes(0) |
| .build(); |
| try { |
| // Create a fake in-progress edit-log in the shared directory |
| URI sharedUri = cluster.getSharedEditsDir(0, 1); |
| File sharedDir = new File(sharedUri.getPath(), "current"); |
| FSImageTestUtil.createAbortedLogWithMkdirs(sharedDir, NUM_DIRS_IN_LOG, 1); |
| |
| assertEditFiles(Collections.singletonList(sharedUri), |
| NNStorage.getInProgressEditsFileName(1)); |
| if (partialTxAtEnd) { |
| FileOutputStream outs = null; |
| try { |
| File editLogFile = |
| new File(sharedDir, NNStorage.getInProgressEditsFileName(1)); |
| outs = new FileOutputStream(editLogFile, true); |
| outs.write(new byte[] { 0x18, 0x00, 0x00, 0x00 } ); |
| LOG.error("editLogFile = " + editLogFile); |
| } finally { |
| IOUtils.cleanup(LOG, outs); |
| } |
| } |
| |
| // Transition one of the NNs to active |
| cluster.transitionToActive(0); |
| |
| // In the transition to active, it should have read the log -- and |
| // hence see one of the dirs we made in the fake log. |
| String testPath = "/dir" + NUM_DIRS_IN_LOG; |
| assertNotNull(cluster.getNameNode(0).getRpcServer().getFileInfo(testPath)); |
| |
| // It also should have finalized that log in the shared directory and started |
| // writing to a new one at the next txid. |
| assertEditFiles(Collections.singletonList(sharedUri), |
| NNStorage.getFinalizedEditsFileName(1, NUM_DIRS_IN_LOG + 1), |
| NNStorage.getInProgressEditsFileName(NUM_DIRS_IN_LOG + 2)); |
| } finally { |
| cluster.shutdown(); |
| } |
| } |
| |
| @Test |
| public void testFailoverFinalizesAndReadsInProgressSimple() |
| throws Exception { |
| testFailoverFinalizesAndReadsInProgress(false); |
| } |
| |
| @Test |
| public void testFailoverFinalizesAndReadsInProgressWithPartialTxAtEnd() |
| throws Exception { |
| testFailoverFinalizesAndReadsInProgress(true); |
| } |
| |
| /** |
| * Check that no edits files are present in the given storage dirs. |
| */ |
| private void assertNoEditFiles(Iterable<URI> dirs) throws IOException { |
| assertEditFiles(dirs, new String[]{}); |
| } |
| |
| /** |
| * Check that the given list of edits files are present in the given storage |
| * dirs. |
| */ |
| private void assertEditFiles(Iterable<URI> dirs, String ... files) |
| throws IOException { |
| for (URI u : dirs) { |
| File editDirRoot = new File(u.getPath()); |
| File editDir = new File(editDirRoot, "current"); |
| GenericTestUtils.assertExists(editDir); |
| if (files.length == 0) { |
| LOG.info("Checking no edit files exist in " + editDir); |
| } else { |
| LOG.info("Checking for following edit files in " + editDir |
| + ": " + Joiner.on(",").join(files)); |
| } |
| |
| GenericTestUtils.assertGlobEquals(editDir, "edits_.*", files); |
| } |
| } |
| } |