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.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertNotNull; 022import static org.junit.jupiter.api.Assertions.assertNotSame; 023import static org.junit.jupiter.api.Assertions.assertNull; 024import static org.junit.jupiter.api.Assertions.assertSame; 025import static org.junit.jupiter.api.Assertions.assertTrue; 026import static org.mockito.Mockito.mock; 027import static org.mockito.Mockito.when; 028 029import java.io.IOException; 030import java.util.ArrayList; 031import java.util.HashMap; 032import java.util.List; 033import java.util.Map; 034import java.util.Random; 035import java.util.TreeMap; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.hbase.ClusterMetrics; 038import org.apache.hadoop.hbase.HConstants; 039import org.apache.hadoop.hbase.HDFSBlocksDistribution; 040import org.apache.hadoop.hbase.HDFSBlocksDistribution.HostAndWeight; 041import org.apache.hadoop.hbase.RegionMetrics; 042import org.apache.hadoop.hbase.ServerMetrics; 043import org.apache.hadoop.hbase.ServerName; 044import org.apache.hadoop.hbase.TableName; 045import org.apache.hadoop.hbase.client.RegionInfo; 046import org.apache.hadoop.hbase.client.RegionInfoBuilder; 047import org.apache.hadoop.hbase.client.TableDescriptor; 048import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 049import org.apache.hadoop.hbase.testclassification.MasterTests; 050import org.apache.hadoop.hbase.testclassification.SmallTests; 051import org.apache.hadoop.hbase.util.Bytes; 052import org.junit.jupiter.api.BeforeAll; 053import org.junit.jupiter.api.BeforeEach; 054import org.junit.jupiter.api.Tag; 055import org.junit.jupiter.api.Test; 056 057@Tag(MasterTests.TAG) 058@Tag(SmallTests.TAG) 059public class TestRegionHDFSBlockLocationFinder { 060 061 private static final Random RNG = new Random(); // This test depends on Random#setSeed 062 private static TableDescriptor TD; 063 private static List<RegionInfo> REGIONS; 064 065 private RegionHDFSBlockLocationFinder finder; 066 067 private static HDFSBlocksDistribution generate(RegionInfo region) { 068 HDFSBlocksDistribution distribution = new HDFSBlocksDistribution(); 069 int seed = region.hashCode(); 070 RNG.setSeed(seed); 071 int size = 1 + RNG.nextInt(10); 072 for (int i = 0; i < size; i++) { 073 distribution.addHostsAndBlockWeight(new String[] { "host-" + i }, 1 + RNG.nextInt(100)); 074 } 075 return distribution; 076 } 077 078 @BeforeAll 079 public static void setUpBeforeClass() { 080 TD = TableDescriptorBuilder.newBuilder(TableName.valueOf("RegionLocationFinder")).build(); 081 int numRegions = 100; 082 REGIONS = new ArrayList<>(numRegions); 083 for (int i = 1; i <= numRegions; i++) { 084 byte[] startKey = i == 0 ? HConstants.EMPTY_START_ROW : Bytes.toBytes(i); 085 byte[] endKey = i == numRegions ? HConstants.EMPTY_BYTE_ARRAY : Bytes.toBytes(i + 1); 086 RegionInfo region = RegionInfoBuilder.newBuilder(TD.getTableName()).setStartKey(startKey) 087 .setEndKey(endKey).build(); 088 REGIONS.add(region); 089 } 090 } 091 092 @BeforeEach 093 public void setUp() { 094 finder = new RegionHDFSBlockLocationFinder(); 095 finder.setClusterInfoProvider(new DummyClusterInfoProvider(null) { 096 097 @Override 098 public TableDescriptor getTableDescriptor(TableName tableName) throws IOException { 099 return TD; 100 } 101 102 @Override 103 public List<RegionInfo> getAssignedRegions() { 104 return REGIONS; 105 } 106 107 @Override 108 public HDFSBlocksDistribution computeHDFSBlocksDistribution(Configuration conf, 109 TableDescriptor tableDescriptor, RegionInfo regionInfo) throws IOException { 110 return generate(regionInfo); 111 } 112 }); 113 } 114 115 @Test 116 public void testMapHostNameToServerName() throws Exception { 117 assertTrue(finder.mapHostNameToServerName(null).isEmpty()); 118 119 List<String> hosts = new ArrayList<>(); 120 for (int i = 0; i < 10; i += 2) { 121 hosts.add("host-" + i); 122 } 123 assertTrue(finder.mapHostNameToServerName(hosts).isEmpty()); 124 125 Map<ServerName, ServerMetrics> serverMetrics = new HashMap<>(); 126 for (int i = 0; i < 10; i += 2) { 127 ServerName sn = ServerName.valueOf("host-" + i, 12345, 12345); 128 serverMetrics.put(sn, null); 129 } 130 ClusterMetrics metrics = mock(ClusterMetrics.class); 131 when(metrics.getLiveServerMetrics()).thenReturn(serverMetrics); 132 133 finder.setClusterMetrics(metrics); 134 List<ServerName> sns = finder.mapHostNameToServerName(hosts); 135 assertEquals(5, sns.size()); 136 for (int i = 0; i < 5; i++) { 137 ServerName sn = sns.get(i); 138 assertEquals("host-" + (2 * i), sn.getHostname()); 139 assertEquals(12345, sn.getPort()); 140 assertEquals(12345, sn.getStartcode()); 141 } 142 } 143 144 @Test 145 public void testRefreshAndWait() throws Exception { 146 finder.getCache().invalidateAll(); 147 for (RegionInfo region : REGIONS) { 148 assertNull(finder.getCache().getIfPresent(region)); 149 } 150 finder.refreshAndWait(REGIONS); 151 for (RegionInfo region : REGIONS) { 152 assertNotNull(finder.getCache().getIfPresent(region)); 153 } 154 } 155 156 private void assertHostAndWeightEquals(HDFSBlocksDistribution expected, 157 HDFSBlocksDistribution actual) { 158 Map<String, HostAndWeight> expectedMap = expected.getHostAndWeights(); 159 Map<String, HostAndWeight> actualMap = actual.getHostAndWeights(); 160 assertEquals(expectedMap.size(), actualMap.size()); 161 expectedMap.forEach((k, expectedHostAndWeight) -> { 162 HostAndWeight actualHostAndWeight = actualMap.get(k); 163 assertEquals(expectedHostAndWeight.getHost(), actualHostAndWeight.getHost()); 164 assertEquals(expectedHostAndWeight.getWeight(), actualHostAndWeight.getWeight()); 165 assertEquals(expectedHostAndWeight.getWeightForSsd(), actualHostAndWeight.getWeightForSsd()); 166 }); 167 } 168 169 @Test 170 public void testGetBlockDistribution() { 171 Map<RegionInfo, HDFSBlocksDistribution> cache = new HashMap<>(); 172 for (RegionInfo region : REGIONS) { 173 HDFSBlocksDistribution hbd = finder.getBlockDistribution(region); 174 assertHostAndWeightEquals(generate(region), hbd); 175 cache.put(region, hbd); 176 } 177 // the instance should be cached 178 for (RegionInfo region : REGIONS) { 179 HDFSBlocksDistribution hbd = finder.getBlockDistribution(region); 180 assertSame(cache.get(region), hbd); 181 } 182 } 183 184 @Test 185 public void testGetTopBlockLocations() { 186 Map<ServerName, ServerMetrics> serverMetrics = new HashMap<>(); 187 for (int i = 0; i < 10; i++) { 188 ServerName sn = ServerName.valueOf("host-" + i, 12345, 12345); 189 serverMetrics.put(sn, null); 190 } 191 ClusterMetrics metrics = mock(ClusterMetrics.class); 192 when(metrics.getLiveServerMetrics()).thenReturn(serverMetrics); 193 finder.setClusterMetrics(metrics); 194 for (RegionInfo region : REGIONS) { 195 List<ServerName> servers = finder.getTopBlockLocations(region); 196 long previousWeight = Long.MAX_VALUE; 197 HDFSBlocksDistribution hbd = generate(region); 198 for (ServerName server : servers) { 199 long weight = hbd.getWeight(server.getHostname()); 200 assertTrue(weight <= previousWeight); 201 previousWeight = weight; 202 } 203 } 204 } 205 206 @Test 207 public void testRefreshRegionsWithChangedLocality() throws InterruptedException { 208 ServerName testServer = ServerName.valueOf("host-0", 12345, 12345); 209 RegionInfo testRegion = REGIONS.get(0); 210 211 Map<RegionInfo, HDFSBlocksDistribution> cache = new HashMap<>(); 212 for (RegionInfo region : REGIONS) { 213 HDFSBlocksDistribution hbd = finder.getBlockDistribution(region); 214 assertHostAndWeightEquals(generate(region), hbd); 215 cache.put(region, hbd); 216 } 217 218 finder 219 .setClusterMetrics(getMetricsWithLocality(testServer, testRegion.getRegionName(), 0.123f)); 220 221 // everything should be 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 ClusterMetrics getMetricsWithLocality(ServerName serverName, byte[] region, 252 float locality) { 253 RegionMetrics regionMetrics = mock(RegionMetrics.class); 254 when(regionMetrics.getDataLocality()).thenReturn(locality); 255 256 Map<byte[], RegionMetrics> regionMetricsMap = new TreeMap<>(Bytes.BYTES_COMPARATOR); 257 regionMetricsMap.put(region, regionMetrics); 258 259 ServerMetrics serverMetrics = mock(ServerMetrics.class); 260 when(serverMetrics.getRegionMetrics()).thenReturn(regionMetricsMap); 261 262 Map<ServerName, ServerMetrics> serverMetricsMap = new HashMap<>(); 263 serverMetricsMap.put(serverName, serverMetrics); 264 265 ClusterMetrics metrics = mock(ClusterMetrics.class); 266 when(metrics.getLiveServerMetrics()).thenReturn(serverMetricsMap); 267 268 return metrics; 269 } 270}