| /* |
| * 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.hbase.master; |
| |
| import edu.umd.cs.findbugs.annotations.Nullable; |
| import java.io.UnsupportedEncodingException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.hbase.ClusterMetrics; |
| import org.apache.hadoop.hbase.HBaseClassTestRule; |
| import org.apache.hadoop.hbase.HBaseTestingUtility; |
| import org.apache.hadoop.hbase.RegionMetrics; |
| import org.apache.hadoop.hbase.ServerMetrics; |
| import org.apache.hadoop.hbase.ServerName; |
| import org.apache.hadoop.hbase.Size; |
| import org.apache.hadoop.hbase.Stoppable; |
| import org.apache.hadoop.hbase.TableName; |
| import org.apache.hadoop.hbase.UserMetrics; |
| import org.apache.hadoop.hbase.client.CompactionState; |
| import org.apache.hadoop.hbase.client.RegionInfo; |
| import org.apache.hadoop.hbase.client.RegionStatesCount; |
| import org.apache.hadoop.hbase.master.assignment.AssignmentManager; |
| import org.apache.hadoop.hbase.replication.ReplicationLoadSink; |
| import org.apache.hadoop.hbase.replication.ReplicationLoadSource; |
| import org.apache.hadoop.hbase.testclassification.MasterTests; |
| import org.apache.hadoop.hbase.testclassification.SmallTests; |
| import org.apache.hadoop.hbase.util.Bytes; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.ClassRule; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| import org.mockito.Mockito; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Test for RegionsRecoveryChore |
| */ |
| @Category({MasterTests.class, SmallTests.class}) |
| public class TestRegionsRecoveryChore { |
| |
| @ClassRule |
| public static final HBaseClassTestRule CLASS_RULE = |
| HBaseClassTestRule.forClass(TestRegionsRecoveryChore.class); |
| |
| private static final Logger LOG = LoggerFactory.getLogger(TestRegionsRecoveryChore.class); |
| |
| private static final HBaseTestingUtility HBASE_TESTING_UTILITY = new HBaseTestingUtility(); |
| |
| private static final String UTF_8_CHARSET = StandardCharsets.UTF_8.name(); |
| |
| private HMaster hMaster; |
| |
| private AssignmentManager assignmentManager; |
| |
| private RegionsRecoveryChore regionsRecoveryChore; |
| |
| private static int regionNo; |
| public static final byte[][] REGION_NAME_LIST = new byte[][]{ |
| new byte[]{114, 101, 103, 105, 111, 110, 50, 49, 95, 51}, |
| new byte[]{114, 101, 103, 105, 111, 110, 50, 53, 95, 51}, |
| new byte[]{114, 101, 103, 105, 111, 110, 50, 54, 95, 52}, |
| new byte[]{114, 101, 103, 105, 111, 110, 51, 50, 95, 53}, |
| new byte[]{114, 101, 103, 105, 111, 110, 51, 49, 95, 52}, |
| new byte[]{114, 101, 103, 105, 111, 110, 51, 48, 95, 51}, |
| new byte[]{114, 101, 103, 105, 111, 110, 50, 48, 95, 50}, |
| new byte[]{114, 101, 103, 105, 111, 110, 50, 52, 95, 50}, |
| new byte[]{114, 101, 103, 105, 111, 110, 50, 57, 95, 50}, |
| new byte[]{114, 101, 103, 105, 111, 110, 51, 53, 95, 50}, |
| new byte[]{114, 101, 103, 105, 111, 110, 49, 48, 56, 95, 49, 49} |
| }; |
| |
| private Configuration getCustomConf() { |
| Configuration conf = HBASE_TESTING_UTILITY.getConfiguration(); |
| conf.setInt("hbase.master.regions.recovery.check.interval", 100); |
| return conf; |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| this.hMaster = Mockito.mock(HMaster.class); |
| this.assignmentManager = Mockito.mock(AssignmentManager.class); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| Mockito.verifyNoMoreInteractions(this.hMaster); |
| Mockito.verifyNoMoreInteractions(this.assignmentManager); |
| } |
| |
| @Test |
| public void testRegionReopensWithStoreRefConfig() throws Exception { |
| regionNo = 0; |
| ClusterMetrics clusterMetrics = TestRegionsRecoveryChore.getClusterMetrics(4); |
| final Map<ServerName, ServerMetrics> serverMetricsMap = |
| clusterMetrics.getLiveServerMetrics(); |
| LOG.debug("All Region Names with refCount...."); |
| for (ServerMetrics serverMetrics : serverMetricsMap.values()) { |
| Map<byte[], RegionMetrics> regionMetricsMap = serverMetrics.getRegionMetrics(); |
| for (RegionMetrics regionMetrics : regionMetricsMap.values()) { |
| LOG.debug("name: " + new String(regionMetrics.getRegionName()) + " refCount: " + |
| regionMetrics.getStoreRefCount()); |
| } |
| } |
| Mockito.when(hMaster.getClusterMetrics()).thenReturn(clusterMetrics); |
| Mockito.when(hMaster.getAssignmentManager()).thenReturn(assignmentManager); |
| for (byte[] regionName : REGION_NAME_LIST) { |
| Mockito.when(assignmentManager.getRegionInfo(regionName)) |
| .thenReturn(TestRegionsRecoveryChore.getRegionInfo(regionName)); |
| } |
| Stoppable stoppable = new StoppableImplementation(); |
| Configuration configuration = getCustomConf(); |
| configuration.setInt("hbase.regions.recovery.store.file.ref.count", 300); |
| regionsRecoveryChore = new RegionsRecoveryChore(stoppable, configuration, hMaster); |
| regionsRecoveryChore.chore(); |
| |
| // Verify that we need to reopen regions of 2 tables |
| Mockito.verify(hMaster, Mockito.times(2)).reopenRegions(Mockito.any(), Mockito.anyList(), |
| Mockito.anyLong(), Mockito.anyLong()); |
| Mockito.verify(hMaster, Mockito.times(1)).getClusterMetrics(); |
| |
| // Verify that we need to reopen total 3 regions that have refCount > 300 |
| Mockito.verify(hMaster, Mockito.times(3)).getAssignmentManager(); |
| Mockito.verify(assignmentManager, Mockito.times(3)) |
| .getRegionInfo(Mockito.any()); |
| } |
| |
| @Test |
| public void testRegionReopensWithLessThreshold() throws Exception { |
| regionNo = 0; |
| ClusterMetrics clusterMetrics = TestRegionsRecoveryChore.getClusterMetrics(4); |
| final Map<ServerName, ServerMetrics> serverMetricsMap = |
| clusterMetrics.getLiveServerMetrics(); |
| LOG.debug("All Region Names with refCount...."); |
| for (ServerMetrics serverMetrics : serverMetricsMap.values()) { |
| Map<byte[], RegionMetrics> regionMetricsMap = serverMetrics.getRegionMetrics(); |
| for (RegionMetrics regionMetrics : regionMetricsMap.values()) { |
| LOG.debug("name: " + new String(regionMetrics.getRegionName()) + " refCount: " + |
| regionMetrics.getStoreRefCount()); |
| } |
| } |
| Mockito.when(hMaster.getClusterMetrics()).thenReturn(clusterMetrics); |
| Mockito.when(hMaster.getAssignmentManager()).thenReturn(assignmentManager); |
| for (byte[] regionName : REGION_NAME_LIST) { |
| Mockito.when(assignmentManager.getRegionInfo(regionName)) |
| .thenReturn(TestRegionsRecoveryChore.getRegionInfo(regionName)); |
| } |
| Stoppable stoppable = new StoppableImplementation(); |
| Configuration configuration = getCustomConf(); |
| configuration.setInt("hbase.regions.recovery.store.file.ref.count", 400); |
| regionsRecoveryChore = new RegionsRecoveryChore(stoppable, configuration, hMaster); |
| regionsRecoveryChore.chore(); |
| |
| // Verify that we need to reopen regions of only 1 table |
| Mockito.verify(hMaster, Mockito.times(1)).reopenRegions(Mockito.any(), Mockito.anyList(), |
| Mockito.anyLong(), Mockito.anyLong()); |
| Mockito.verify(hMaster, Mockito.times(1)).getClusterMetrics(); |
| |
| // Verify that we need to reopen only 1 region with refCount > 400 |
| Mockito.verify(hMaster, Mockito.times(1)).getAssignmentManager(); |
| Mockito.verify(assignmentManager, Mockito.times(1)) |
| .getRegionInfo(Mockito.any()); |
| } |
| |
| @Test |
| public void testRegionReopensWithoutStoreRefConfig() throws Exception { |
| regionNo = 0; |
| ClusterMetrics clusterMetrics = TestRegionsRecoveryChore.getClusterMetrics(10); |
| final Map<ServerName, ServerMetrics> serverMetricsMap = |
| clusterMetrics.getLiveServerMetrics(); |
| LOG.debug("All Region Names with refCount...."); |
| for (ServerMetrics serverMetrics : serverMetricsMap.values()) { |
| Map<byte[], RegionMetrics> regionMetricsMap = serverMetrics.getRegionMetrics(); |
| for (RegionMetrics regionMetrics : regionMetricsMap.values()) { |
| LOG.debug("name: " + new String(regionMetrics.getRegionName()) + " refCount: " + |
| regionMetrics.getStoreRefCount()); |
| } |
| } |
| Mockito.when(hMaster.getClusterMetrics()).thenReturn(clusterMetrics); |
| Mockito.when(hMaster.getAssignmentManager()).thenReturn(assignmentManager); |
| for (byte[] regionName : REGION_NAME_LIST) { |
| Mockito.when(assignmentManager.getRegionInfo(regionName)) |
| .thenReturn(TestRegionsRecoveryChore.getRegionInfo(regionName)); |
| } |
| Stoppable stoppable = new StoppableImplementation(); |
| Configuration configuration = getCustomConf(); |
| configuration.unset("hbase.regions.recovery.store.file.ref.count"); |
| regionsRecoveryChore = new RegionsRecoveryChore(stoppable, configuration, hMaster); |
| regionsRecoveryChore.chore(); |
| |
| // Verify that by default the feature is turned off so no regions |
| // should be reopened |
| Mockito.verify(hMaster, Mockito.times(0)).reopenRegions(Mockito.any(), Mockito.anyList(), |
| Mockito.anyLong(), Mockito.anyLong()); |
| |
| // default maxCompactedStoreFileRefCount is -1 (no regions to be reopened using AM) |
| Mockito.verify(hMaster, Mockito.times(0)).getAssignmentManager(); |
| Mockito.verify(assignmentManager, Mockito.times(0)) |
| .getRegionInfo(Mockito.any()); |
| } |
| |
| private static ClusterMetrics getClusterMetrics(int noOfLiveServer) { |
| ClusterMetrics clusterMetrics = new ClusterMetrics() { |
| |
| @Nullable |
| @Override |
| public String getHBaseVersion() { |
| return null; |
| } |
| |
| @Override |
| public List<ServerName> getDeadServerNames() { |
| return null; |
| } |
| |
| @Override |
| public Map<ServerName, ServerMetrics> getLiveServerMetrics() { |
| Map<ServerName, ServerMetrics> liveServerMetrics = new HashMap<>(); |
| for (int i = 0; i < noOfLiveServer; i++) { |
| ServerName serverName = ServerName.valueOf("rs_" + i, 16010, 12345); |
| liveServerMetrics.put(serverName, TestRegionsRecoveryChore.getServerMetrics(i + 3)); |
| } |
| return liveServerMetrics; |
| } |
| |
| @Nullable |
| @Override |
| public ServerName getMasterName() { |
| return null; |
| } |
| |
| @Override |
| public List<ServerName> getBackupMasterNames() { |
| return null; |
| } |
| |
| @Override |
| public List<RegionState> getRegionStatesInTransition() { |
| return null; |
| } |
| |
| @Nullable |
| @Override |
| public String getClusterId() { |
| return null; |
| } |
| |
| @Override |
| public List<String> getMasterCoprocessorNames() { |
| return null; |
| } |
| |
| @Nullable |
| @Override |
| public Boolean getBalancerOn() { |
| return null; |
| } |
| |
| @Override |
| public int getMasterInfoPort() { |
| return 0; |
| } |
| |
| @Override |
| public List<ServerName> getServersName() { |
| return null; |
| } |
| |
| @Override |
| public Map<TableName, RegionStatesCount> getTableRegionStatesCount() { |
| return null; |
| } |
| |
| }; |
| return clusterMetrics; |
| } |
| |
| private static ServerMetrics getServerMetrics(int noOfRegions) { |
| ServerMetrics serverMetrics = new ServerMetrics() { |
| |
| @Override |
| public ServerName getServerName() { |
| return null; |
| } |
| |
| @Override |
| public long getRequestCountPerSecond() { |
| return 0; |
| } |
| |
| @Override |
| public long getRequestCount() { |
| return 0; |
| } |
| |
| @Override |
| public Size getUsedHeapSize() { |
| return null; |
| } |
| |
| @Override |
| public Size getMaxHeapSize() { |
| return null; |
| } |
| |
| @Override |
| public int getInfoServerPort() { |
| return 0; |
| } |
| |
| @Override |
| public List<ReplicationLoadSource> getReplicationLoadSourceList() { |
| return null; |
| } |
| |
| @Override |
| public Map<String, List<ReplicationLoadSource>> getReplicationLoadSourceMap() { |
| return null; |
| } |
| |
| @Nullable |
| @Override |
| public ReplicationLoadSink getReplicationLoadSink() { |
| return null; |
| } |
| |
| @Override |
| public Map<byte[], RegionMetrics> getRegionMetrics() { |
| Map<byte[], RegionMetrics> regionMetricsMap = new HashMap<>(); |
| for (int i = 0; i < noOfRegions; i++) { |
| byte[] regionName = Bytes.toBytes("region" + regionNo + "_" + i); |
| regionMetricsMap.put(regionName, |
| TestRegionsRecoveryChore.getRegionMetrics(regionName, 100 * i)); |
| ++regionNo; |
| } |
| return regionMetricsMap; |
| } |
| |
| @Override public Map<byte[], UserMetrics> getUserMetrics() { |
| return new HashMap<>(); |
| } |
| |
| @Override |
| public Set<String> getCoprocessorNames() { |
| return null; |
| } |
| |
| @Override |
| public long getReportTimestamp() { |
| return 0; |
| } |
| |
| @Override |
| public long getLastReportTimestamp() { |
| return 0; |
| } |
| |
| }; |
| return serverMetrics; |
| } |
| |
| private static RegionMetrics getRegionMetrics(byte[] regionName, int compactedStoreRefCount) { |
| RegionMetrics regionMetrics = new RegionMetrics() { |
| |
| @Override |
| public byte[] getRegionName() { |
| return regionName; |
| } |
| |
| @Override |
| public int getStoreCount() { |
| return 0; |
| } |
| |
| @Override |
| public int getStoreFileCount() { |
| return 0; |
| } |
| |
| @Override |
| public Size getStoreFileSize() { |
| return null; |
| } |
| |
| @Override |
| public Size getMemStoreSize() { |
| return null; |
| } |
| |
| @Override |
| public long getReadRequestCount() { |
| return 0; |
| } |
| |
| @Override |
| public long getWriteRequestCount() { |
| return 0; |
| } |
| |
| @Override |
| public long getCpRequestCount() { |
| return 0; |
| } |
| |
| @Override |
| public long getFilteredReadRequestCount() { |
| return 0; |
| } |
| |
| @Override |
| public Size getStoreFileIndexSize() { |
| return null; |
| } |
| |
| @Override |
| public Size getStoreFileRootLevelIndexSize() { |
| return null; |
| } |
| |
| @Override |
| public Size getStoreFileUncompressedDataIndexSize() { |
| return null; |
| } |
| |
| @Override |
| public Size getBloomFilterSize() { |
| return null; |
| } |
| |
| @Override |
| public long getCompactingCellCount() { |
| return 0; |
| } |
| |
| @Override |
| public long getCompactedCellCount() { |
| return 0; |
| } |
| |
| @Override |
| public long getCompletedSequenceId() { |
| return 0; |
| } |
| |
| @Override |
| public Map<byte[], Long> getStoreSequenceId() { |
| return null; |
| } |
| |
| @Override |
| public Size getUncompressedStoreFileSize() { |
| return null; |
| } |
| |
| @Override |
| public float getDataLocality() { |
| return 0; |
| } |
| |
| @Override |
| public long getLastMajorCompactionTimestamp() { |
| return 0; |
| } |
| |
| @Override |
| public int getStoreRefCount() { |
| return compactedStoreRefCount; |
| } |
| |
| @Override |
| public int getMaxCompactedStoreFileRefCount() { |
| return compactedStoreRefCount; |
| } |
| |
| @Override |
| public float getDataLocalityForSsd() { |
| return 0; |
| } |
| |
| @Override |
| public long getBlocksLocalWeight() { |
| return 0; |
| } |
| |
| @Override |
| public long getBlocksLocalWithSsdWeight() { |
| return 0; |
| } |
| |
| @Override |
| public long getBlocksTotalWeight() { |
| return 0; |
| } |
| |
| @Override |
| public CompactionState getCompactionState() { |
| return null; |
| } |
| }; |
| return regionMetrics; |
| } |
| |
| private static RegionInfo getRegionInfo(byte[] regionNameBytes) { |
| RegionInfo regionInfo = new RegionInfo() { |
| |
| @Override |
| public String getShortNameToLog() { |
| return null; |
| } |
| |
| @Override |
| public long getRegionId() { |
| return 0; |
| } |
| |
| @Override |
| public byte[] getRegionName() { |
| return new byte[0]; |
| } |
| |
| @Override |
| public String getRegionNameAsString() { |
| try { |
| return new String(regionNameBytes, UTF_8_CHARSET); |
| } catch (UnsupportedEncodingException e) { |
| return ""; |
| } |
| } |
| |
| @Override |
| public String getEncodedName() { |
| return null; |
| } |
| |
| @Override |
| public byte[] getEncodedNameAsBytes() { |
| return new byte[0]; |
| } |
| |
| @Override |
| public byte[] getStartKey() { |
| return new byte[0]; |
| } |
| |
| @Override |
| public byte[] getEndKey() { |
| return new byte[0]; |
| } |
| |
| @Override |
| public TableName getTable() { |
| String regionName; |
| try { |
| regionName = new String(regionNameBytes, UTF_8_CHARSET); |
| } catch (UnsupportedEncodingException e) { |
| regionName = ""; |
| } |
| int regionNo = Integer.parseInt(regionName.split("_")[1]); |
| TableName tableName = TableName.valueOf("table_" + regionNo % 3); |
| return tableName; |
| } |
| |
| @Override |
| public int getReplicaId() { |
| return 0; |
| } |
| |
| @Override |
| public boolean isSplit() { |
| return false; |
| } |
| |
| @Override |
| public boolean isOffline() { |
| return false; |
| } |
| |
| @Override |
| public boolean isSplitParent() { |
| return false; |
| } |
| |
| @Override |
| public boolean isMetaRegion() { |
| return false; |
| } |
| |
| @Override |
| public boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey) { |
| return false; |
| } |
| |
| @Override |
| public boolean containsRow(byte[] row) { |
| return false; |
| } |
| |
| }; |
| return regionInfo; |
| } |
| |
| /** |
| * Simple helper class that just keeps track of whether or not its stopped. |
| */ |
| private static class StoppableImplementation implements Stoppable { |
| |
| private volatile boolean stop = false; |
| |
| @Override |
| public void stop(String why) { |
| this.stop = true; |
| } |
| |
| @Override |
| public boolean isStopped() { |
| return this.stop; |
| } |
| |
| } |
| |
| } |