/** * 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.*; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import java.io.File; import java.io.IOException; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hdfs.server.common.Storage.StorageDirectory; import org.apache.hadoop.hdfs.server.namenode.NNStorage.NameNodeDirType; import static org.apache.hadoop.hdfs.server.namenode.NNStorage.getInProgressEditsFileName; import static org.apache.hadoop.hdfs.server.namenode.NNStorage.getFinalizedEditsFileName; import static org.apache.hadoop.hdfs.server.namenode.NNStorage.getImageFileName; import org.apache.hadoop.hdfs.server.namenode.FileJournalManager.EditLogFile; import org.apache.hadoop.hdfs.server.namenode.FSImageStorageInspector.FSImageFile; import org.apache.hadoop.hdfs.server.namenode.FSImageTransactionalStorageInspector.TransactionalLoadPlan; import org.apache.hadoop.hdfs.server.namenode.FSImageTransactionalStorageInspector.LogGroup; import org.apache.hadoop.hdfs.server.namenode.FSImageStorageInspector.LoadPlan; import org.junit.Test; import org.mockito.Mockito; public class TestFSImageStorageInspector { private static final Log LOG = LogFactory.getLog( TestFSImageStorageInspector.class); /** * Simple test with image, edits, and inprogress edits */ @Test public void testCurrentStorageInspector() throws IOException { FSImageTransactionalStorageInspector inspector = new FSImageTransactionalStorageInspector(); StorageDirectory mockDir = FSImageTestUtil.mockStorageDirectory( NameNodeDirType.IMAGE_AND_EDITS, false, "/foo/current/" + getImageFileName(123), "/foo/current/" + getFinalizedEditsFileName(123, 456), "/foo/current/" + getImageFileName(456), "/foo/current/" + getInProgressEditsFileName(457)); inspector.inspectDirectory(mockDir); mockLogValidation(inspector, "/foo/current/" + getInProgressEditsFileName(457), 10); assertEquals(2, inspector.foundEditLogs.size()); assertEquals(2, inspector.foundImages.size()); assertTrue(inspector.foundEditLogs.get(1).isInProgress()); FSImageFile latestImage = inspector.getLatestImage(); assertEquals(456, latestImage.txId); assertSame(mockDir, latestImage.sd); assertTrue(inspector.isUpgradeFinalized()); LoadPlan plan = inspector.createLoadPlan(); LOG.info("Plan: " + plan); assertEquals(new File("/foo/current/"+getImageFileName(456)), plan.getImageFile()); assertArrayEquals(new File[] { new File("/foo/current/" + getInProgressEditsFileName(457)) }, plan.getEditsFiles().toArray(new File[0])); } /** * Test that we check for gaps in txids when devising a load plan. */ @Test public void testPlanWithGaps() throws IOException { FSImageTransactionalStorageInspector inspector = new FSImageTransactionalStorageInspector(); StorageDirectory mockDir = FSImageTestUtil.mockStorageDirectory( NameNodeDirType.IMAGE_AND_EDITS, false, "/foo/current/" + getImageFileName(123), "/foo/current/" + getImageFileName(456), "/foo/current/" + getFinalizedEditsFileName(457,900), "/foo/current/" + getFinalizedEditsFileName(901,950), "/foo/current/" + getFinalizedEditsFileName(952,1000)); // <-- missing edit 951! inspector.inspectDirectory(mockDir); try { inspector.createLoadPlan(); fail("Didn't throw IOE trying to load with gaps in edits"); } catch (IOException ioe) { assertTrue(ioe.getMessage().contains( "would start at txid 951 but starts at txid 952")); } } /** * Test the case where an in-progress log comes in the middle of a sequence * of logs */ @Test public void testPlanWithInProgressInMiddle() throws IOException { FSImageTransactionalStorageInspector inspector = new FSImageTransactionalStorageInspector(); StorageDirectory mockDir = FSImageTestUtil.mockStorageDirectory( NameNodeDirType.IMAGE_AND_EDITS, false, "/foo/current/" + getImageFileName(123), "/foo/current/" + getImageFileName(456), "/foo/current/" + getFinalizedEditsFileName(457,900), "/foo/current/" + getInProgressEditsFileName(901), // <-- inprogress in middle "/foo/current/" + getFinalizedEditsFileName(952,1000)); inspector.inspectDirectory(mockDir); mockLogValidation(inspector, "/foo/current/" + getInProgressEditsFileName(901), 51); LoadPlan plan = inspector.createLoadPlan(); LOG.info("Plan: " + plan); assertEquals(new File("/foo/current/" + getImageFileName(456)), plan.getImageFile()); assertArrayEquals(new File[] { new File("/foo/current/" + getFinalizedEditsFileName(457,900)), new File("/foo/current/" + getInProgressEditsFileName(901)), new File("/foo/current/" + getFinalizedEditsFileName(952,1000)) }, plan.getEditsFiles().toArray(new File[0])); } /** * Test case for the usual case where no recovery of a log group is necessary * (i.e all logs have the same start and end txids and finalized) */ @Test public void testLogGroupRecoveryNoop() throws IOException { FSImageTransactionalStorageInspector inspector = new FSImageTransactionalStorageInspector(); inspector.inspectDirectory( mockDirectoryWithEditLogs("/foo1/current/" + getFinalizedEditsFileName(123,456))); inspector.inspectDirectory( mockDirectoryWithEditLogs("/foo2/current/" + getFinalizedEditsFileName(123,456))); inspector.inspectDirectory( mockDirectoryWithEditLogs("/foo3/current/" + getFinalizedEditsFileName(123,456))); LogGroup lg = inspector.logGroups.get(123L); assertEquals(3, lg.logs.size()); lg.planRecovery(); assertFalse(lg.logs.get(0).isCorrupt()); assertFalse(lg.logs.get(1).isCorrupt()); assertFalse(lg.logs.get(2).isCorrupt()); } /** * Test case where we have some in-progress and some finalized logs * for a given txid. */ @Test public void testLogGroupRecoveryMixed() throws IOException { FSImageTransactionalStorageInspector inspector = new FSImageTransactionalStorageInspector(); inspector.inspectDirectory( mockDirectoryWithEditLogs("/foo1/current/" + getFinalizedEditsFileName(123,456))); inspector.inspectDirectory( mockDirectoryWithEditLogs("/foo2/current/" + getFinalizedEditsFileName(123,456))); inspector.inspectDirectory( mockDirectoryWithEditLogs("/foo3/current/" + getInProgressEditsFileName(123))); inspector.inspectDirectory(FSImageTestUtil.mockStorageDirectory( NameNodeDirType.IMAGE, false, "/foo4/current/" + getImageFileName(122))); LogGroup lg = inspector.logGroups.get(123L); assertEquals(3, lg.logs.size()); EditLogFile inProgressLog = lg.logs.get(2); assertTrue(inProgressLog.isInProgress()); LoadPlan plan = inspector.createLoadPlan(); // Check that it was marked corrupt. assertFalse(lg.logs.get(0).isCorrupt()); assertFalse(lg.logs.get(1).isCorrupt()); assertTrue(lg.logs.get(2).isCorrupt()); // Calling recover should move it aside inProgressLog = spy(inProgressLog); Mockito.doNothing().when(inProgressLog).moveAsideCorruptFile(); lg.logs.set(2, inProgressLog); plan.doRecovery(); Mockito.verify(inProgressLog).moveAsideCorruptFile(); } /** * Test case where we have finalized logs with different end txids */ @Test public void testLogGroupRecoveryInconsistentEndTxIds() throws IOException { FSImageTransactionalStorageInspector inspector = new FSImageTransactionalStorageInspector(); inspector.inspectDirectory( mockDirectoryWithEditLogs("/foo1/current/" + getFinalizedEditsFileName(123,456))); inspector.inspectDirectory( mockDirectoryWithEditLogs("/foo2/current/" + getFinalizedEditsFileName(123,678))); LogGroup lg = inspector.logGroups.get(123L); assertEquals(2, lg.logs.size()); try { lg.planRecovery(); fail("Didn't throw IOE on inconsistent end txids"); } catch (IOException ioe) { assertTrue(ioe.getMessage().contains("More than one ending txid")); } } /** * Test case where we have only in-progress logs and need to synchronize * based on valid length. */ @Test public void testLogGroupRecoveryInProgress() throws IOException { String paths[] = new String[] { "/foo1/current/" + getInProgressEditsFileName(123), "/foo2/current/" + getInProgressEditsFileName(123), "/foo3/current/" + getInProgressEditsFileName(123) }; FSImageTransactionalStorageInspector inspector = new FSImageTransactionalStorageInspector(); inspector.inspectDirectory(mockDirectoryWithEditLogs(paths[0])); inspector.inspectDirectory(mockDirectoryWithEditLogs(paths[1])); inspector.inspectDirectory(mockDirectoryWithEditLogs(paths[2])); // Inject spies to return the valid counts we would like to see mockLogValidation(inspector, paths[0], 2000); mockLogValidation(inspector, paths[1], 2000); mockLogValidation(inspector, paths[2], 1000); LogGroup lg = inspector.logGroups.get(123L); assertEquals(3, lg.logs.size()); lg.planRecovery(); // Check that the short one was marked corrupt assertFalse(lg.logs.get(0).isCorrupt()); assertFalse(lg.logs.get(1).isCorrupt()); assertTrue(lg.logs.get(2).isCorrupt()); // Calling recover should move it aside EditLogFile badLog = lg.logs.get(2); Mockito.doNothing().when(badLog).moveAsideCorruptFile(); Mockito.doNothing().when(lg.logs.get(0)).finalizeLog(); Mockito.doNothing().when(lg.logs.get(1)).finalizeLog(); lg.recover(); Mockito.verify(badLog).moveAsideCorruptFile(); Mockito.verify(lg.logs.get(0)).finalizeLog(); Mockito.verify(lg.logs.get(1)).finalizeLog(); } /** * Mock out the log at the given path to return a specified number * of transactions upon validation. */ private void mockLogValidation( FSImageTransactionalStorageInspector inspector, String path, int numValidTransactions) throws IOException { for (LogGroup lg : inspector.logGroups.values()) { List<EditLogFile> logs = lg.logs; for (int i = 0; i < logs.size(); i++) { EditLogFile log = logs.get(i); if (log.getFile().getPath().equals(path)) { // mock out its validation EditLogFile spyLog = spy(log); doReturn(new FSEditLogLoader.EditLogValidation(-1, numValidTransactions)) .when(spyLog).validateLog(); logs.set(i, spyLog); return; } } } fail("No log found to mock out at " + path); } /** * Test when edits and image are in separate directories. */ @Test public void testCurrentSplitEditsAndImage() throws IOException { FSImageTransactionalStorageInspector inspector = new FSImageTransactionalStorageInspector(); StorageDirectory mockImageDir = FSImageTestUtil.mockStorageDirectory( NameNodeDirType.IMAGE, false, "/foo/current/" + getImageFileName(123)); StorageDirectory mockImageDir2 = FSImageTestUtil.mockStorageDirectory( NameNodeDirType.IMAGE, false, "/foo2/current/" + getImageFileName(456)); StorageDirectory mockEditsDir = FSImageTestUtil.mockStorageDirectory( NameNodeDirType.EDITS, false, "/foo3/current/" + getFinalizedEditsFileName(123, 456), "/foo3/current/" + getInProgressEditsFileName(457)); inspector.inspectDirectory(mockImageDir); inspector.inspectDirectory(mockEditsDir); inspector.inspectDirectory(mockImageDir2); mockLogValidation(inspector, "/foo3/current/" + getInProgressEditsFileName(457), 2); assertEquals(2, inspector.foundEditLogs.size()); assertEquals(2, inspector.foundImages.size()); assertTrue(inspector.foundEditLogs.get(1).isInProgress()); assertTrue(inspector.isUpgradeFinalized()); // Check plan TransactionalLoadPlan plan = (TransactionalLoadPlan)inspector.createLoadPlan(); FSImageFile pickedImage = plan.image; assertEquals(456, pickedImage.txId); assertSame(mockImageDir2, pickedImage.sd); assertEquals(new File("/foo2/current/" + getImageFileName(456)), plan.getImageFile()); assertArrayEquals(new File[] { new File("/foo3/current/" + getInProgressEditsFileName(457)) }, plan.getEditsFiles().toArray(new File[0])); } /** * Test case where an in-progress log is in an earlier name directory * than a finalized log. Previously, getEditLogManifest wouldn't * see this log. */ @Test public void testLogManifestInProgressComesFirst() throws IOException { FSImageTransactionalStorageInspector inspector = new FSImageTransactionalStorageInspector(); inspector.inspectDirectory( mockDirectoryWithEditLogs("/foo1/current/" + getFinalizedEditsFileName(2622,2623), "/foo1/current/" + getFinalizedEditsFileName(2624,2625), "/foo1/current/" + getInProgressEditsFileName(2626))); inspector.inspectDirectory( mockDirectoryWithEditLogs("/foo2/current/" + getFinalizedEditsFileName(2622,2623), "/foo2/current/" + getFinalizedEditsFileName(2624,2625), "/foo2/current/" + getFinalizedEditsFileName(2626,2627), "/foo2/current/" + getFinalizedEditsFileName(2628,2629))); } static StorageDirectory mockDirectoryWithEditLogs(String... fileNames) { return FSImageTestUtil.mockStorageDirectory(NameNodeDirType.EDITS, false, fileNames); } }