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}