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.util;
019
020import java.util.ArrayList;
021import java.util.List;
022import java.util.concurrent.TimeUnit;
023import java.util.stream.Collectors;
024import org.apache.hadoop.hbase.HBaseClassTestRule;
025import org.apache.hadoop.hbase.HBaseTestingUtility;
026import org.apache.hadoop.hbase.HRegionLocation;
027import org.apache.hadoop.hbase.MiniHBaseCluster;
028import org.apache.hadoop.hbase.ServerName;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.client.Admin;
031import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
032import org.apache.hadoop.hbase.client.Put;
033import org.apache.hadoop.hbase.client.RegionInfoBuilder;
034import org.apache.hadoop.hbase.client.Table;
035import org.apache.hadoop.hbase.client.TableDescriptor;
036import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
037import org.apache.hadoop.hbase.master.RegionState;
038import org.apache.hadoop.hbase.regionserver.HRegion;
039import org.apache.hadoop.hbase.regionserver.HRegionServer;
040import org.apache.hadoop.hbase.testclassification.LargeTests;
041import org.apache.hadoop.hbase.testclassification.MiscTests;
042import org.apache.hadoop.hbase.zookeeper.MetaTableLocator;
043import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
044import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
045import org.junit.After;
046import org.junit.AfterClass;
047import org.junit.Assert;
048import org.junit.Before;
049import org.junit.BeforeClass;
050import org.junit.ClassRule;
051import org.junit.Rule;
052import org.junit.Test;
053import org.junit.experimental.categories.Category;
054import org.junit.rules.TestName;
055import org.slf4j.Logger;
056import org.slf4j.LoggerFactory;
057
058/**
059 * Tests for Region Mover Load/Unload functionality with and without ack mode and also to test
060 * exclude functionality useful for rack decommissioning
061 */
062@Category({ MiscTests.class, LargeTests.class })
063public class TestRegionMover2 {
064
065  @ClassRule
066  public static final HBaseClassTestRule CLASS_RULE =
067    HBaseClassTestRule.forClass(TestRegionMover2.class);
068
069  @Rule
070  public TestName name = new TestName();
071
072  private static final Logger LOG = LoggerFactory.getLogger(TestRegionMover2.class);
073
074  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
075
076  @BeforeClass
077  public static void setUpBeforeClass() throws Exception {
078    TEST_UTIL.getConfiguration().setInt("hbase.client.retries.number", 4);
079    TEST_UTIL.startMiniCluster(3);
080    TEST_UTIL.getAdmin().balancerSwitch(false, true);
081  }
082
083  @AfterClass
084  public static void tearDownAfterClass() throws Exception {
085    TEST_UTIL.shutdownMiniCluster();
086  }
087
088  @Before
089  public void setUp() throws Exception {
090    final TableName tableName = TableName.valueOf(name.getMethodName());
091    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tableName)
092      .setColumnFamily(ColumnFamilyDescriptorBuilder.of("fam1")).build();
093    int startKey = 0;
094    int endKey = 80000;
095    TEST_UTIL.getAdmin().createTable(tableDesc, Bytes.toBytes(startKey), Bytes.toBytes(endKey), 9);
096  }
097
098  @After
099  public void tearDown() throws Exception {
100    final TableName tableName = TableName.valueOf(name.getMethodName());
101    TEST_UTIL.getAdmin().disableTable(tableName);
102    TEST_UTIL.getAdmin().deleteTable(tableName);
103  }
104
105  @Test
106  public void testWithSplitRegions() throws Exception {
107    final TableName tableName = TableName.valueOf(name.getMethodName());
108    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
109    Admin admin = TEST_UTIL.getAdmin();
110    Table table = TEST_UTIL.getConnection().getTable(tableName);
111    List<Put> puts = new ArrayList<>();
112    for (int i = 10; i < 50000; i++) {
113      puts.add(new Put(Bytes.toBytes(i)).addColumn(Bytes.toBytes("fam1"), Bytes.toBytes("q1"),
114        Bytes.toBytes("val_" + i)));
115    }
116    table.put(puts);
117    admin.flush(tableName);
118    admin.compact(tableName);
119    HRegionServer regionServer = cluster.getRegionServer(0);
120    String rsName = regionServer.getServerName().getAddress().toString();
121    int numRegions = regionServer.getNumberOfOnlineRegions();
122    List<HRegion> hRegions = regionServer.getRegions().stream()
123      .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
124      .collect(Collectors.toList());
125
126    RegionMover.RegionMoverBuilder rmBuilder =
127      new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
128        .maxthreads(8);
129    try (RegionMover rm = rmBuilder.build()) {
130      LOG.debug("Unloading {}", regionServer.getServerName());
131      rm.unload();
132      Assert.assertEquals(0, regionServer.getNumberOfOnlineRegions());
133      LOG.debug("Successfully Unloaded, now Loading");
134      HRegion hRegion = hRegions.get(1);
135      if (hRegion.getRegionInfo().getStartKey().length == 0) {
136        hRegion = hRegions.get(0);
137      }
138      int startKey = 0;
139      int endKey = Integer.MAX_VALUE;
140      if (hRegion.getRegionInfo().getStartKey().length > 0) {
141        startKey = Bytes.toInt(hRegion.getRegionInfo().getStartKey());
142      }
143      if (hRegion.getRegionInfo().getEndKey().length > 0) {
144        endKey = Bytes.toInt(hRegion.getRegionInfo().getEndKey());
145      }
146      int midKey = startKey + (endKey - startKey) / 2;
147      admin.splitRegionAsync(hRegion.getRegionInfo().getRegionName(), Bytes.toBytes(midKey)).get(5,
148        TimeUnit.SECONDS);
149      Assert.assertTrue(rm.load());
150      Assert.assertEquals(numRegions - 1, regionServer.getNumberOfOnlineRegions());
151    }
152  }
153
154  @Test
155  public void testFailedRegionMove() throws Exception {
156    final TableName tableName = TableName.valueOf(name.getMethodName());
157    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
158    Admin admin = TEST_UTIL.getAdmin();
159    Table table = TEST_UTIL.getConnection().getTable(tableName);
160    List<Put> puts = new ArrayList<>();
161    for (int i = 0; i < 1000; i++) {
162      puts.add(new Put(Bytes.toBytes("rowkey_" + i)).addColumn(Bytes.toBytes("fam1"),
163        Bytes.toBytes("q1"), Bytes.toBytes("val_" + i)));
164    }
165    table.put(puts);
166    admin.flush(tableName);
167    HRegionServer regionServer = cluster.getRegionServer(0);
168    String rsName = regionServer.getServerName().getAddress().toString();
169    List<HRegion> hRegions = regionServer.getRegions().stream()
170      .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
171      .collect(Collectors.toList());
172    RegionMover.RegionMoverBuilder rmBuilder =
173      new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
174        .maxthreads(8);
175    try (RegionMover rm = rmBuilder.build()) {
176      LOG.debug("Unloading {}", regionServer.getServerName());
177      rm.unload();
178      Assert.assertEquals(0, regionServer.getNumberOfOnlineRegions());
179      LOG.debug("Successfully Unloaded, now Loading");
180      admin.offline(hRegions.get(0).getRegionInfo().getRegionName());
181      // loading regions will fail because of offline region
182      Assert.assertFalse(rm.load());
183    }
184  }
185
186  public void loadDummyDataInTable(TableName tableName) throws Exception {
187    Admin admin = TEST_UTIL.getAdmin();
188    Table table = TEST_UTIL.getConnection().getTable(tableName);
189    List<Put> puts = new ArrayList<>();
190    for (int i = 0; i < 1000; i++) {
191      puts.add(new Put(Bytes.toBytes("rowkey_" + i)).addColumn(Bytes.toBytes("fam1"),
192        Bytes.toBytes("q1"), Bytes.toBytes("val_" + i)));
193    }
194    table.put(puts);
195    admin.flush(tableName);
196  }
197
198  @Test
199  public void testIsolateSingleRegionOnTheSameServer() throws Exception {
200    final TableName tableName = TableName.valueOf(name.getMethodName());
201    loadDummyDataInTable(tableName);
202    ServerName sourceServerName = findSourceServerName(tableName);
203    // Isolating 1 region on the same region server.
204    regionIsolationOperation(sourceServerName, sourceServerName, 1, false);
205  }
206
207  @Test
208  public void testIsolateSingleRegionOnTheDifferentServer() throws Exception {
209    final TableName tableName = TableName.valueOf(name.getMethodName());
210    loadDummyDataInTable(tableName);
211    ServerName sourceServerName = findSourceServerName(tableName);
212    ServerName destinationServerName = findDestinationServerName(sourceServerName);
213    // Isolating 1 region on the different region server.
214    regionIsolationOperation(sourceServerName, destinationServerName, 1, false);
215  }
216
217  @Test
218  public void testIsolateMultipleRegionsOnTheSameServer() throws Exception {
219    final TableName tableName = TableName.valueOf(name.getMethodName());
220    loadDummyDataInTable(tableName);
221    ServerName sourceServerName = findSourceServerName(tableName);
222    // Isolating 2 regions on the same region server.
223    regionIsolationOperation(sourceServerName, sourceServerName, 2, false);
224  }
225
226  @Test
227  public void testIsolateMultipleRegionsOnTheDifferentServer() throws Exception {
228    final TableName tableName = TableName.valueOf(name.getMethodName());
229    loadDummyDataInTable(tableName);
230    // Isolating 2 regions on the different region server.
231    ServerName sourceServerName = findSourceServerName(tableName);
232    ServerName destinationServerName = findDestinationServerName(sourceServerName);
233    regionIsolationOperation(sourceServerName, destinationServerName, 2, false);
234  }
235
236  @Test
237  public void testIsolateMetaOnTheSameSever() throws Exception {
238    ServerName metaServerSource = findMetaRSLocation();
239    regionIsolationOperation(metaServerSource, metaServerSource, 1, true);
240  }
241
242  @Test
243  public void testIsolateMetaOnTheDifferentServer() throws Exception {
244    ServerName metaServerSource = findMetaRSLocation();
245    ServerName metaServerDestination = findDestinationServerName(metaServerSource);
246    regionIsolationOperation(metaServerSource, metaServerDestination, 1, true);
247  }
248
249  @Test
250  public void testIsolateMetaAndRandomRegionOnTheMetaServer() throws Exception {
251    final TableName tableName = TableName.valueOf(name.getMethodName());
252    loadDummyDataInTable(tableName);
253    ServerName metaServerSource = findMetaRSLocation();
254    ServerName randomSeverRegion = findSourceServerName(tableName);
255    regionIsolationOperation(randomSeverRegion, metaServerSource, 2, true);
256  }
257
258  @Test
259  public void testIsolateMetaAndRandomRegionOnTheRandomServer() throws Exception {
260    final TableName tableName = TableName.valueOf(name.getMethodName());
261    loadDummyDataInTable(tableName);
262    ServerName randomSeverRegion = findSourceServerName(tableName);
263    regionIsolationOperation(randomSeverRegion, randomSeverRegion, 2, true);
264  }
265
266  public ServerName findMetaRSLocation() throws Exception {
267    ZKWatcher zkWatcher = new ZKWatcher(TEST_UTIL.getConfiguration(), null, null);
268    List<HRegionLocation> result = new ArrayList<>();
269    for (String znode : zkWatcher.getMetaReplicaNodes()) {
270      String path = ZNodePaths.joinZNode(zkWatcher.getZNodePaths().baseZNode, znode);
271      int replicaId = zkWatcher.getZNodePaths().getMetaReplicaIdFromPath(path);
272      RegionState state = MetaTableLocator.getMetaRegionState(zkWatcher, replicaId);
273      result.add(new HRegionLocation(state.getRegion(), state.getServerName()));
274    }
275    return result.get(0).getServerName();
276  }
277
278  public ServerName findSourceServerName(TableName tableName) throws Exception {
279    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
280    int numOfRS = cluster.getNumLiveRegionServers();
281    ServerName sourceServer = null;
282    for (int i = 0; i < numOfRS; i++) {
283      HRegionServer regionServer = cluster.getRegionServer(i);
284      List<HRegion> hRegions = regionServer.getRegions().stream()
285        .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
286        .collect(Collectors.toList());
287      if (hRegions.size() >= 2) {
288        sourceServer = regionServer.getServerName();
289        break;
290      }
291    }
292    if (sourceServer == null) {
293      throw new Exception(
294        "This shouln't happen, No RS found with more than 2 regions of table : " + tableName);
295    }
296    return sourceServer;
297  }
298
299  public ServerName findDestinationServerName(ServerName sourceServerName) throws Exception {
300    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
301    ServerName destinationServerName = null;
302    int numOfRS = cluster.getNumLiveRegionServers();
303    for (int i = 0; i < numOfRS; i++) {
304      destinationServerName = cluster.getRegionServer(i).getServerName();
305      if (!destinationServerName.equals(sourceServerName)) {
306        break;
307      }
308    }
309    if (destinationServerName == null) {
310      throw new Exception("This shouldn't happen, No RS found which is different than source RS");
311    }
312    return destinationServerName;
313  }
314
315  public void regionIsolationOperation(ServerName sourceServerName,
316    ServerName destinationServerName, int numRegionsToIsolate, boolean isolateMetaAlso)
317    throws Exception {
318    final TableName tableName = TableName.valueOf(name.getMethodName());
319    MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
320    Admin admin = TEST_UTIL.getAdmin();
321    HRegionServer sourceRS = cluster.getRegionServer(sourceServerName);
322    List<HRegion> hRegions = sourceRS.getRegions().stream()
323      .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
324      .collect(Collectors.toList());
325    List<String> listOfRegionIDsToIsolate = new ArrayList<>();
326    for (int i = 0; i < numRegionsToIsolate; i++) {
327      listOfRegionIDsToIsolate.add(hRegions.get(i).getRegionInfo().getEncodedName());
328    }
329
330    if (isolateMetaAlso) {
331      listOfRegionIDsToIsolate.remove(0);
332      listOfRegionIDsToIsolate.add(RegionInfoBuilder.FIRST_META_REGIONINFO.getEncodedName());
333    }
334
335    HRegionServer destinationRS = cluster.getRegionServer(destinationServerName);
336    String destinationRSName = destinationRS.getServerName().getAddress().toString();
337    RegionMover.RegionMoverBuilder rmBuilder =
338      new RegionMover.RegionMoverBuilder(destinationRSName, TEST_UTIL.getConfiguration()).ack(true)
339        .maxthreads(8).isolateRegionIdArray(listOfRegionIDsToIsolate);
340    try (RegionMover rm = rmBuilder.build()) {
341      LOG.debug("Unloading {} except regions : {}", destinationRS.getServerName(),
342        listOfRegionIDsToIsolate);
343      rm.isolateRegions();
344      Assert.assertEquals(numRegionsToIsolate, destinationRS.getNumberOfOnlineRegions());
345      List<HRegion> onlineRegions = destinationRS.getRegions();
346      for (int i = 0; i < numRegionsToIsolate; i++) {
347        Assert.assertTrue(
348          listOfRegionIDsToIsolate.contains(onlineRegions.get(i).getRegionInfo().getEncodedName()));
349      }
350      LOG.debug("Successfully Isolated " + listOfRegionIDsToIsolate.size() + " regions : "
351        + listOfRegionIDsToIsolate + " on " + destinationRS.getServerName());
352    } finally {
353      admin.recommissionRegionServer(destinationRS.getServerName(), null);
354    }
355  }
356}