001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.master.balancer; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertNotNull; 022import static org.junit.Assert.assertNotSame; 023import static org.junit.Assert.assertSame; 024import static org.junit.Assert.assertTrue; 025import static org.mockito.Mockito.mock; 026import static org.mockito.Mockito.when; 027 028import java.util.ArrayList; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.Random; 033import java.util.TreeMap; 034import org.apache.hadoop.hbase.ClusterMetrics; 035import org.apache.hadoop.hbase.HBaseClassTestRule; 036import org.apache.hadoop.hbase.HBaseTestingUtility; 037import org.apache.hadoop.hbase.HConstants; 038import org.apache.hadoop.hbase.HDFSBlocksDistribution; 039import org.apache.hadoop.hbase.MiniHBaseCluster; 040import org.apache.hadoop.hbase.RegionMetrics; 041import org.apache.hadoop.hbase.ServerMetrics; 042import org.apache.hadoop.hbase.ServerName; 043import org.apache.hadoop.hbase.TableName; 044import org.apache.hadoop.hbase.client.RegionInfo; 045import org.apache.hadoop.hbase.client.RegionInfoBuilder; 046import org.apache.hadoop.hbase.client.Table; 047import org.apache.hadoop.hbase.client.TableDescriptor; 048import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 049import org.apache.hadoop.hbase.regionserver.HRegion; 050import org.apache.hadoop.hbase.regionserver.HRegionServer; 051import org.apache.hadoop.hbase.testclassification.MasterTests; 052import org.apache.hadoop.hbase.testclassification.MediumTests; 053import org.apache.hadoop.hbase.util.Bytes; 054import org.junit.AfterClass; 055import org.junit.BeforeClass; 056import org.junit.ClassRule; 057import org.junit.Test; 058import org.junit.experimental.categories.Category; 059 060@Category({ MasterTests.class, MediumTests.class }) 061public class TestRegionLocationFinder { 062 063 @ClassRule 064 public static final HBaseClassTestRule CLASS_RULE = 065 HBaseClassTestRule.forClass(TestRegionLocationFinder.class); 066 067 private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 068 private static MiniHBaseCluster cluster; 069 070 private final static TableName tableName = TableName.valueOf("table"); 071 private final static byte[] FAMILY = Bytes.toBytes("cf"); 072 private static Table table; 073 private final static int ServerNum = 5; 074 075 private static RegionLocationFinder finder = new RegionLocationFinder(); 076 077 @BeforeClass 078 public static void setUpBeforeClass() throws Exception { 079 cluster = TEST_UTIL.startMiniCluster(ServerNum); 080 table = TEST_UTIL.createTable(tableName, FAMILY, HBaseTestingUtility.KEYS_FOR_HBA_CREATE_TABLE); 081 TEST_UTIL.waitTableAvailable(tableName, 1000); 082 TEST_UTIL.loadTable(table, FAMILY); 083 084 for (int i = 0; i < ServerNum; i++) { 085 HRegionServer server = cluster.getRegionServer(i); 086 for (HRegion region : server.getRegions(tableName)) { 087 region.flush(true); 088 } 089 } 090 091 finder.setConf(TEST_UTIL.getConfiguration()); 092 finder.setServices(cluster.getMaster()); 093 finder.setClusterMetrics(cluster.getMaster().getClusterMetrics()); 094 } 095 096 @AfterClass 097 public static void tearDownAfterClass() throws Exception { 098 table.close(); 099 TEST_UTIL.deleteTable(tableName); 100 TEST_UTIL.shutdownMiniCluster(); 101 } 102 103 @Test 104 public void testInternalGetTopBlockLocation() throws Exception { 105 for (int i = 0; i < ServerNum; i++) { 106 HRegionServer server = cluster.getRegionServer(i); 107 for (HRegion region : server.getRegions(tableName)) { 108 // get region's hdfs block distribution by region and RegionLocationFinder, 109 // they should have same result 110 HDFSBlocksDistribution blocksDistribution1 = region.getHDFSBlocksDistribution(); 111 HDFSBlocksDistribution blocksDistribution2 = 112 finder.getBlockDistribution(region.getRegionInfo()); 113 assertEquals(blocksDistribution1.getUniqueBlocksTotalWeight(), 114 blocksDistribution2.getUniqueBlocksTotalWeight()); 115 if (blocksDistribution1.getUniqueBlocksTotalWeight() != 0) { 116 assertEquals(blocksDistribution1.getTopHosts().get(0), 117 blocksDistribution2.getTopHosts().get(0)); 118 } 119 } 120 } 121 } 122 123 @Test 124 public void testMapHostNameToServerName() throws Exception { 125 List<String> topHosts = new ArrayList<>(); 126 for (int i = 0; i < ServerNum; i++) { 127 HRegionServer server = cluster.getRegionServer(i); 128 String serverHost = server.getServerName().getHostname(); 129 if (!topHosts.contains(serverHost)) { 130 topHosts.add(serverHost); 131 } 132 } 133 List<ServerName> servers = finder.mapHostNameToServerName(topHosts); 134 // mini cluster, all rs in one host 135 assertEquals(1, topHosts.size()); 136 for (int i = 0; i < ServerNum; i++) { 137 ServerName server = cluster.getRegionServer(i).getServerName(); 138 assertTrue(servers.contains(server)); 139 } 140 } 141 142 @Test 143 public void testGetTopBlockLocations() throws Exception { 144 for (int i = 0; i < ServerNum; i++) { 145 HRegionServer server = cluster.getRegionServer(i); 146 for (HRegion region : server.getRegions(tableName)) { 147 List<ServerName> servers = finder.getTopBlockLocations(region.getRegionInfo()); 148 // test table may have empty region 149 if (region.getHDFSBlocksDistribution().getUniqueBlocksTotalWeight() == 0) { 150 continue; 151 } 152 List<String> topHosts = region.getHDFSBlocksDistribution().getTopHosts(); 153 // rs and datanode may have different host in local machine test 154 if (!topHosts.contains(server.getServerName().getHostname())) { 155 continue; 156 } 157 for (int j = 0; j < ServerNum; j++) { 158 ServerName serverName = cluster.getRegionServer(j).getServerName(); 159 assertTrue(servers.contains(serverName)); 160 } 161 } 162 } 163 } 164 165 @Test 166 public void testRefreshAndWait() throws Exception { 167 finder.getCache().invalidateAll(); 168 for (int i = 0; i < ServerNum; i++) { 169 HRegionServer server = cluster.getRegionServer(i); 170 List<HRegion> regions = server.getRegions(tableName); 171 if (regions.size() <= 0) { 172 continue; 173 } 174 List<RegionInfo> regionInfos = new ArrayList<>(regions.size()); 175 for (HRegion region : regions) { 176 regionInfos.add(region.getRegionInfo()); 177 } 178 finder.refreshAndWait(regionInfos); 179 for (RegionInfo regionInfo : regionInfos) { 180 assertNotNull(finder.getCache().getIfPresent(regionInfo)); 181 } 182 } 183 } 184 185 @Test 186 public void testRefreshRegionsWithChangedLocality() throws InterruptedException { 187 TableDescriptor table = 188 TableDescriptorBuilder.newBuilder(TableName.valueOf("RegionLocationFinder")).build(); 189 190 int numRegions = 100; 191 List<RegionInfo> regions = new ArrayList<>(numRegions); 192 193 for (int i = 1; i <= numRegions; i++) { 194 byte[] startKey = i == 0 ? HConstants.EMPTY_START_ROW : Bytes.toBytes(i); 195 byte[] endKey = i == numRegions ? HConstants.EMPTY_BYTE_ARRAY : Bytes.toBytes(i + 1); 196 RegionInfo region = RegionInfoBuilder.newBuilder(table.getTableName()).setStartKey(startKey) 197 .setEndKey(endKey).build(); 198 regions.add(region); 199 } 200 201 ServerName testServer = ServerName.valueOf("host-0", 12345, 12345); 202 RegionInfo testRegion = regions.get(0); 203 204 RegionLocationFinder finder = new RegionLocationFinder() { 205 @Override 206 protected HDFSBlocksDistribution internalGetTopBlockLocation(RegionInfo region) { 207 return generate(region); 208 } 209 }; 210 211 // cache for comparison later 212 Map<RegionInfo, HDFSBlocksDistribution> cache = new HashMap<>(); 213 for (RegionInfo region : regions) { 214 HDFSBlocksDistribution hbd = finder.getBlockDistribution(region); 215 cache.put(region, hbd); 216 } 217 218 finder 219 .setClusterMetrics(getMetricsWithLocality(testServer, testRegion.getRegionName(), 0.123f)); 220 221 // everything should be same as cached, because metrics were null before 222 for (RegionInfo region : regions) { 223 HDFSBlocksDistribution hbd = finder.getBlockDistribution(region); 224 assertSame(cache.get(region), hbd); 225 } 226 227 finder 228 .setClusterMetrics(getMetricsWithLocality(testServer, testRegion.getRegionName(), 0.345f)); 229 230 // cache refresh happens in a background thread, so we need to wait for the value to 231 // update before running assertions. 232 long now = System.currentTimeMillis(); 233 HDFSBlocksDistribution cached = cache.get(testRegion); 234 HDFSBlocksDistribution newValue; 235 do { 236 Thread.sleep(1_000); 237 newValue = finder.getBlockDistribution(testRegion); 238 } while (cached == newValue && System.currentTimeMillis() - now < 30_000); 239 240 // locality changed just for our test region, so it should no longer be the same 241 for (RegionInfo region : regions) { 242 HDFSBlocksDistribution hbd = finder.getBlockDistribution(region); 243 if (region.equals(testRegion)) { 244 assertNotSame(cache.get(region), hbd); 245 } else { 246 assertSame(cache.get(region), hbd); 247 } 248 } 249 } 250 251 private static HDFSBlocksDistribution generate(RegionInfo region) { 252 HDFSBlocksDistribution distribution = new HDFSBlocksDistribution(); 253 int seed = region.hashCode(); 254 Random rand = new Random(seed); 255 int size = 1 + rand.nextInt(10); 256 for (int i = 0; i < size; i++) { 257 distribution.addHostsAndBlockWeight(new String[] { "host-" + i }, 1 + rand.nextInt(100)); 258 } 259 return distribution; 260 } 261 262 private ClusterMetrics getMetricsWithLocality(ServerName serverName, byte[] region, 263 float locality) { 264 RegionMetrics regionMetrics = mock(RegionMetrics.class); 265 when(regionMetrics.getDataLocality()).thenReturn(locality); 266 267 Map<byte[], RegionMetrics> regionMetricsMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); 268 regionMetricsMap.put(region, regionMetrics); 269 270 ServerMetrics serverMetrics = mock(ServerMetrics.class); 271 when(serverMetrics.getRegionMetrics()).thenReturn(regionMetricsMap); 272 273 Map<ServerName, ServerMetrics> serverMetricsMap = new HashMap<>(); 274 serverMetricsMap.put(serverName, serverMetrics); 275 276 ClusterMetrics metrics = mock(ClusterMetrics.class); 277 when(metrics.getLiveServerMetrics()).thenReturn(serverMetricsMap); 278 279 return metrics; 280 } 281}