/** * 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 java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hdfs.protocol.DatanodeID; import org.apache.hadoop.hdfs.server.namenode.StandbySafeMode.SafeModeState; import static org.junit.Assert.*; import org.junit.Test; public class TestStandbySafeModeImpl { private static StandbySafeMode safeMode; private static Configuration conf; private static MyNamesystem namesystem; private static final Random random = new Random(); private static Log LOG = LogFactory.getLog(TestStandbySafeModeImpl.class); private class MyNamesystem extends FSNamesystem { public long totalBlocks = 0; @Override public long getBlocksTotal() { return totalBlocks; } @Override public boolean isDatanodeDead(DatanodeDescriptor node) { return false; } @Override public DatanodeDescriptor getDatanode(DatanodeID nodeID) { return new DatanodeDescriptor(); } @Override public void processMisReplicatedBlocks() { } @Override public void writeLock() { } @Override public void writeUnlock() { } } private void setUp(String name) throws Exception { LOG.info("------------------- test: " + name + " START ----------------"); conf = new Configuration(); namesystem = new MyNamesystem(); safeMode = new StandbySafeMode(conf, namesystem); } private static DatanodeID generateRandomDatanodeID() { String nodeName = "" + random.nextLong(); return new DatanodeID(nodeName); } private static class ModificationThread extends Thread { private boolean running = true; public void run() { while (running) { List<DatanodeID> datanodes = new ArrayList<DatanodeID>(); for (int i = 0; i < 50; i++) { DatanodeID node = generateRandomDatanodeID(); datanodes.add(node); safeMode.reportHeartBeat(node); } for (DatanodeID node : datanodes) { if (random.nextBoolean()) { safeMode.reportPrimaryCleared(node); } } } } public void shutdown() { running = false; } } @Test public void testConcurrentModification() throws Exception { setUp("testConcurrentModification"); namesystem.totalBlocks = 100; namesystem.blocksSafe = 100; safeMode.setSafeModeStateForTesting(SafeModeState.FAILOVER_IN_PROGRESS); List<DatanodeID> datanodes = new ArrayList<DatanodeID>(); for (int i = 0; i < 100; i++) { DatanodeID node = generateRandomDatanodeID(); datanodes.add(node); safeMode.reportHeartBeat(node); } ModificationThread t = new ModificationThread(); t.start(); for (int i = 0; i < 100000; i++) { safeMode.canLeave(); } t.shutdown(); } @Test public void testBlocks() throws Exception { setUp("testBlocks"); assertTrue(safeMode.isOn()); assertFalse(safeMode.canLeave()); namesystem.totalBlocks = 100; namesystem.blocksSafe = 100; assertFalse(safeMode.canLeave()); safeMode.setSafeModeStateForTesting(SafeModeState.FAILOVER_IN_PROGRESS); assertTrue(safeMode.canLeave()); } @Test public void testReports() throws Exception { setUp("testReports"); assertTrue(safeMode.isOn()); assertFalse(safeMode.canLeave()); int totalNodes = 100; List <DatanodeID> datanodes = new ArrayList<DatanodeID>(); for (int i = 0; i < totalNodes; i++) { DatanodeID node = generateRandomDatanodeID(); datanodes.add(node); safeMode.reportHeartBeat(node); } for (DatanodeID node : datanodes) { safeMode.reportPrimaryCleared(node); } assertFalse(safeMode.canLeave()); safeMode.setSafeModeStateForTesting(SafeModeState.FAILOVER_IN_PROGRESS); assertTrue(safeMode.canLeave()); } @Test public void testEarlyExit() throws Exception { setUp("testEarlyExit"); namesystem.totalBlocks = 100; namesystem.blocksSafe = 100; try { safeMode.leave(false); } catch (RuntimeException e) { LOG.info("Expected exception", e); return; } fail("Did not throw " + SafeModeException.class); } @Test public void testRandomReports() throws Exception { setUp("testRandomReports"); int totalNodes = 10; List <DatanodeID> datanodes = new ArrayList<DatanodeID>(); for (int i = 0; i < totalNodes; i++) { DatanodeID node = generateRandomDatanodeID(); datanodes.add(node); } assertFalse(safeMode.canLeave()); safeMode.setSafeModeStateForTesting(SafeModeState.FAILOVER_IN_PROGRESS); Set<DatanodeID> expectedR = new HashSet<DatanodeID>(); Set<DatanodeID> expectedH = new HashSet<DatanodeID>(); for (DatanodeID node : datanodes) { // Add live node. if (random.nextBoolean()) { safeMode.addLiveNodeForTesting(node); expectedH.add(node); } // Report heartbeat. if (random.nextBoolean()) { int times = 1; // random.nextInt(3); for (int i = 0; i < times; i++) { safeMode.reportHeartBeat(node); expectedR.add(node); expectedH.remove(node); } } // Report primaryClear. if (random.nextBoolean()) { int times = 1;// random.nextInt(3); for (int i = 0; i < times; i++) { safeMode.reportPrimaryCleared(node); expectedR.remove(node); } } } LOG.info("expected : " + expectedR.size() + " actual : " + safeMode.getOutStandingReports().size()); LOG.info("expected : " + expectedH.size() + " actual : " + safeMode.getOutStandingHeartbeats().size()); assertTrue(expectedR.equals(safeMode.getOutStandingReports())); assertTrue(expectedH.equals(safeMode.getOutStandingHeartbeats())); if (expectedR.size() == 0 && expectedH.size() == 0) { assertTrue(safeMode.canLeave()); } else { assertFalse(safeMode.canLeave()); } } @Test public void testBlocksNotSufficient() throws Exception { setUp("testBlocksNotSufficient"); namesystem.totalBlocks = 100; namesystem.blocksSafe = 50; int totalNodes = 100; List<DatanodeID> datanodes = new ArrayList<DatanodeID>(); for (int i = 0; i < totalNodes; i++) { DatanodeID node = generateRandomDatanodeID(); datanodes.add(node); safeMode.reportHeartBeat(node); safeMode.reportPrimaryCleared(node); } assertFalse(safeMode.canLeave()); safeMode.setSafeModeStateForTesting(SafeModeState.FAILOVER_IN_PROGRESS); assertFalse(safeMode.canLeave()); } @Test public void testReportsNotSufficient() throws Exception { setUp("testReportsNotSufficient"); namesystem.totalBlocks = 100; namesystem.blocksSafe = 100; assertFalse(safeMode.canLeave()); safeMode.setSafeModeStateForTesting(SafeModeState.FAILOVER_IN_PROGRESS); int totalNodes = 100; List<DatanodeID> datanodes = new ArrayList<DatanodeID>(); for (int i = 0; i < totalNodes; i++) { DatanodeID node = generateRandomDatanodeID(); datanodes.add(node); safeMode.reportHeartBeat(node); if (random.nextBoolean()) { safeMode.reportPrimaryCleared(node); } } assertFalse(safeMode.canLeave()); } @Test public void testAllSufficient() throws Exception { setUp("testAllSufficient"); namesystem.totalBlocks = 100; namesystem.blocksSafe = 100; assertFalse(safeMode.canLeave()); safeMode.setSafeModeStateForTesting(SafeModeState.FAILOVER_IN_PROGRESS); int totalNodes = 100; List<DatanodeID> datanodes = new ArrayList<DatanodeID>(); for (int i = 0; i < totalNodes; i++) { DatanodeID node = generateRandomDatanodeID(); datanodes.add(node); safeMode.reportHeartBeat(node); safeMode.reportPrimaryCleared(node); } assertTrue(safeMode.canLeave()); } @Test public void testProcessRBWReports() throws Exception { setUp("testProcessRBWReports"); // at startup the safemode is in BEFORE_FAILOVER state // we do not process RBW reports assertFalse(safeMode.shouldProcessRBWReports()); // in other states we process the RBW reports safeMode.setSafeModeStateForTesting(SafeModeState.FAILOVER_IN_PROGRESS); assertTrue(safeMode.shouldProcessRBWReports()); safeMode.setSafeModeStateForTesting(SafeModeState.AFTER_FAILOVER); assertTrue(safeMode.shouldProcessRBWReports()); // regular namenode safemode always allows RBW reports SafeModeInfo nnsm = new NameNodeSafeModeInfo(conf, namesystem); assertTrue(nnsm.shouldProcessRBWReports()); } @Test public void testInitReplicationQueues() throws Exception { setUp("testInitReplicationQueues"); // initializing replication queues not allowed here assertFailure(SafeModeState.BEFORE_FAILOVER); assertFailure(SafeModeState.AFTER_FAILOVER); assertFailure(SafeModeState.LEAVING_SAFEMODE); // initisalizing only allowed in this state safeMode.setSafeModeStateForTesting(SafeModeState.FAILOVER_IN_PROGRESS); safeMode.initializeReplicationQueues(); } private void assertFailure(SafeModeState state) { try { safeMode.setSafeModeStateForTesting(state); safeMode.initializeReplicationQueues(); } catch (RuntimeException e) { LOG.info("Expected exception: " + e.getMessage()); } } }