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 static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertNotNull;
023import static org.junit.jupiter.api.Assertions.assertTrue;
024
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.List;
028import java.util.concurrent.TimeUnit;
029import java.util.stream.Collectors;
030import org.apache.hadoop.hbase.HBaseTestingUtil;
031import org.apache.hadoop.hbase.HRegionLocation;
032import org.apache.hadoop.hbase.ServerName;
033import org.apache.hadoop.hbase.SingleProcessHBaseCluster;
034import org.apache.hadoop.hbase.TableName;
035import org.apache.hadoop.hbase.client.Admin;
036import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
037import org.apache.hadoop.hbase.client.Put;
038import org.apache.hadoop.hbase.client.RegionInfo;
039import org.apache.hadoop.hbase.client.RegionInfoBuilder;
040import org.apache.hadoop.hbase.client.Table;
041import org.apache.hadoop.hbase.client.TableDescriptor;
042import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
043import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
044import org.apache.hadoop.hbase.master.assignment.TransitRegionStateProcedure;
045import org.apache.hadoop.hbase.master.procedure.ProcedureSyncWait;
046import org.apache.hadoop.hbase.regionserver.HRegion;
047import org.apache.hadoop.hbase.regionserver.HRegionServer;
048import org.apache.hadoop.hbase.testclassification.LargeTests;
049import org.apache.hadoop.hbase.testclassification.MiscTests;
050import org.junit.jupiter.api.AfterAll;
051import org.junit.jupiter.api.AfterEach;
052import org.junit.jupiter.api.BeforeAll;
053import org.junit.jupiter.api.BeforeEach;
054import org.junit.jupiter.api.Tag;
055import org.junit.jupiter.api.Test;
056import org.junit.jupiter.api.TestInfo;
057import org.slf4j.Logger;
058import org.slf4j.LoggerFactory;
059
060/**
061 * Tests for Region Mover Load/Unload functionality with and without ack mode and also to test
062 * exclude functionality useful for rack decommissioning
063 */
064@Tag(MiscTests.TAG)
065@Tag(LargeTests.TAG)
066public class TestRegionMover2 {
067
068  private static final String CF = "fam1";
069
070  private String testMethodName;
071
072  private static final Logger LOG = LoggerFactory.getLogger(TestRegionMover2.class);
073  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
074
075  @BeforeAll
076  public static void setUpBeforeClass() throws Exception {
077    TEST_UTIL.startMiniCluster(3);
078    TEST_UTIL.getAdmin().balancerSwitch(false, true);
079  }
080
081  @AfterAll
082  public static void tearDownAfterClass() throws Exception {
083    TEST_UTIL.shutdownMiniCluster();
084  }
085
086  @BeforeEach
087  public void setUp(TestInfo testInfo) throws Exception {
088    testMethodName = testInfo.getTestMethod().get().getName();
089    createTable(testMethodName);
090  }
091
092  @AfterEach
093  public void tearDown() throws Exception {
094    final TableName tableName = TableName.valueOf(testMethodName);
095    TEST_UTIL.getAdmin().disableTable(tableName);
096    TEST_UTIL.getAdmin().deleteTable(tableName);
097  }
098
099  private TableName createTable(String name) throws IOException {
100    final TableName tableName = TableName.valueOf(name);
101    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tableName)
102      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(CF)).build();
103    int startKey = 0;
104    int endKey = 80000;
105    TEST_UTIL.getAdmin().createTable(tableDesc, Bytes.toBytes(startKey), Bytes.toBytes(endKey), 9);
106    return tableName;
107  }
108
109  @Test
110  public void testWithMergedRegions() throws Exception {
111    final TableName tableName = TableName.valueOf(testMethodName);
112    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
113    Admin admin = TEST_UTIL.getAdmin();
114    Table table = TEST_UTIL.getConnection().getTable(tableName);
115    List<Put> puts = createPuts(10000);
116    table.put(puts);
117    admin.flush(tableName);
118    HRegionServer regionServer = null;
119    RegionInfo regionA = null;
120    RegionInfo regionB = null;
121    for (JVMClusterUtil.RegionServerThread rst : cluster.getRegionServerThreads()) {
122      HRegionServer rs = rst.getRegionServer();
123      for (HRegion region : rs.getRegions(tableName)) {
124        if (!region.getRegionInfo().isLast()) {
125          regionServer = rs;
126          regionA = region.getRegionInfo();
127          regionB = admin.getRegions(tableName).stream()
128            .filter(ri -> Bytes.equals(ri.getStartKey(), region.getRegionInfo().getEndKey()))
129            .findFirst().get();
130          break;
131        }
132      }
133    }
134    assertNotNull(regionServer);
135    // if regionB is not this regionServer, move it
136    AssignmentManager am = cluster.getMaster().getAssignmentManager();
137    while (
138      !regionServer.getServerName()
139        .equals(am.getRegionStates().getRegionState(regionB).getServerName())
140    ) {
141      TransitRegionStateProcedure trsp =
142        am.createMoveRegionProcedure(regionB, regionServer.getServerName());
143      ProcedureSyncWait.submitAndWaitProcedure(cluster.getMaster().getMasterProcedureExecutor(),
144        trsp);
145    }
146
147    String rsName = regionServer.getServerName().getAddress().toString();
148    int numRegions = regionServer.getNumberOfOnlineRegions();
149    RegionMover.RegionMoverBuilder rmBuilder =
150      new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
151        .maxthreads(8);
152    try (RegionMover rm = rmBuilder.build()) {
153      LOG.debug("Unloading {}", regionServer.getServerName());
154      rm.unload();
155      assertEquals(0, regionServer.getNumberOfOnlineRegions());
156      LOG.debug("Successfully Unloaded, now Loading");
157      admin
158        .mergeRegionsAsync(new byte[][] { regionA.getRegionName(), regionB.getRegionName() }, true)
159        .get(15, TimeUnit.SECONDS);
160      assertTrue(rm.load());
161      assertEquals(numRegions - 2, regionServer.getNumberOfOnlineRegions());
162    }
163  }
164
165  @Test
166  public void testWithSplitRegions() throws Exception {
167    final TableName tableName = TableName.valueOf(testMethodName);
168    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
169    Admin admin = TEST_UTIL.getAdmin();
170    Table table = TEST_UTIL.getConnection().getTable(tableName);
171    List<Put> puts = createPuts(50000);
172    table.put(puts);
173    admin.flush(tableName);
174    admin.compact(tableName);
175    HRegionServer regionServer = cluster.getRegionServer(0);
176    String rsName = regionServer.getServerName().getAddress().toString();
177    int numRegions = regionServer.getNumberOfOnlineRegions();
178    List<HRegion> hRegions = regionServer.getRegions().stream()
179      .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
180      .collect(Collectors.toList());
181
182    RegionMover.RegionMoverBuilder rmBuilder =
183      new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
184        .maxthreads(8);
185    try (RegionMover rm = rmBuilder.build()) {
186      LOG.debug("Unloading {}", regionServer.getServerName());
187      rm.unload();
188      assertEquals(0, regionServer.getNumberOfOnlineRegions());
189      LOG.debug("Successfully Unloaded, now Loading");
190      HRegion hRegion = hRegions.get(1);
191      if (hRegion.getRegionInfo().getStartKey().length == 0) {
192        hRegion = hRegions.get(0);
193      }
194      int startKey = 0;
195      int endKey = Integer.MAX_VALUE;
196      if (hRegion.getRegionInfo().getStartKey().length > 0) {
197        startKey = Bytes.toInt(hRegion.getRegionInfo().getStartKey());
198      }
199      if (hRegion.getRegionInfo().getEndKey().length > 0) {
200        endKey = Bytes.toInt(hRegion.getRegionInfo().getEndKey());
201      }
202      int midKey = startKey + (endKey - startKey) / 2;
203      admin.splitRegionAsync(hRegion.getRegionInfo().getRegionName(), Bytes.toBytes(midKey)).get(5,
204        TimeUnit.SECONDS);
205      assertTrue(rm.load());
206      assertEquals(numRegions - 1, regionServer.getNumberOfOnlineRegions());
207    }
208  }
209
210  @Test
211  public void testFailedRegionMove() throws Exception {
212    final TableName tableName = TableName.valueOf(testMethodName);
213    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
214    Admin admin = TEST_UTIL.getAdmin();
215    Table table = TEST_UTIL.getConnection().getTable(tableName);
216    List<Put> puts = createPuts(1000);
217    table.put(puts);
218    admin.flush(tableName);
219    HRegionServer regionServer = cluster.getRegionServer(0);
220    String rsName = regionServer.getServerName().getAddress().toString();
221    List<HRegion> hRegions = regionServer.getRegions().stream()
222      .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
223      .collect(Collectors.toList());
224    RegionMover.RegionMoverBuilder rmBuilder =
225      new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
226        .maxthreads(8);
227    try (RegionMover rm = rmBuilder.build()) {
228      LOG.debug("Unloading {}", regionServer.getServerName());
229      rm.unload();
230      assertEquals(0, regionServer.getNumberOfOnlineRegions());
231      LOG.debug("Successfully Unloaded, now Loading");
232      admin.offline(hRegions.get(0).getRegionInfo().getRegionName());
233      // loading regions will fail because of offline region
234      assertFalse(rm.load());
235    }
236  }
237
238  @Test
239  public void testDeletedTable() throws Exception {
240    TableName tableNameToDelete = createTable(testMethodName + "ToDelete");
241    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
242    HRegionServer regionServer = cluster.getRegionServer(0);
243    String rsName = regionServer.getServerName().getAddress().toString();
244    RegionMover.RegionMoverBuilder rmBuilder =
245      new RegionMover.RegionMoverBuilder(rsName, TEST_UTIL.getConfiguration()).ack(true)
246        .maxthreads(8);
247    try (Admin admin = TEST_UTIL.getAdmin(); RegionMover rm = rmBuilder.build()) {
248      LOG.debug("Unloading {}", regionServer.getServerName());
249      rm.unload();
250      assertEquals(0, regionServer.getNumberOfOnlineRegions());
251      LOG.debug("Successfully Unloaded, now delete table");
252      admin.disableTable(tableNameToDelete);
253      admin.deleteTable(tableNameToDelete);
254      assertTrue(rm.load());
255    }
256  }
257
258  public void loadDummyDataInTable(TableName tableName) throws Exception {
259    Admin admin = TEST_UTIL.getAdmin();
260    Table table = TEST_UTIL.getConnection().getTable(tableName);
261    List<Put> puts = createPuts(1000);
262    table.put(puts);
263    admin.flush(tableName);
264  }
265
266  @Test
267  public void testIsolateSingleRegionOnTheSameServer() throws Exception {
268    final TableName tableName = TableName.valueOf(testMethodName);
269    loadDummyDataInTable(tableName);
270    ServerName sourceServerName = findSourceServerName(tableName);
271    // Isolating 1 region on the same region server.
272    regionIsolationOperation(sourceServerName, sourceServerName, 1, false);
273  }
274
275  @Test
276  public void testIsolateSingleRegionOnTheDifferentServer() throws Exception {
277    final TableName tableName = TableName.valueOf(testMethodName);
278    loadDummyDataInTable(tableName);
279    ServerName sourceServerName = findSourceServerName(tableName);
280    ServerName destinationServerName = findDestinationServerName(sourceServerName);
281    // Isolating 1 region on the different region server.
282    regionIsolationOperation(sourceServerName, destinationServerName, 1, false);
283  }
284
285  @Test
286  public void testIsolateMultipleRegionsOnTheSameServer() throws Exception {
287    final TableName tableName = TableName.valueOf(testMethodName);
288    loadDummyDataInTable(tableName);
289    ServerName sourceServerName = findSourceServerName(tableName);
290    // Isolating 2 regions on the same region server.
291    regionIsolationOperation(sourceServerName, sourceServerName, 2, false);
292  }
293
294  @Test
295  public void testIsolateMultipleRegionsOnTheDifferentServer() throws Exception {
296    final TableName tableName = TableName.valueOf(testMethodName);
297    loadDummyDataInTable(tableName);
298    // Isolating 2 regions on the different region server.
299    ServerName sourceServerName = findSourceServerName(tableName);
300    ServerName destinationServerName = findDestinationServerName(sourceServerName);
301    regionIsolationOperation(sourceServerName, destinationServerName, 2, false);
302  }
303
304  @Test
305  public void testIsolateMetaOnTheSameSever() throws Exception {
306    ServerName metaServerSource = findMetaRSLocation();
307    regionIsolationOperation(metaServerSource, metaServerSource, 1, true);
308  }
309
310  @Test
311  public void testIsolateMetaOnTheDifferentServer() throws Exception {
312    ServerName metaServerSource = findMetaRSLocation();
313    ServerName metaServerDestination = findDestinationServerName(metaServerSource);
314    regionIsolationOperation(metaServerSource, metaServerDestination, 1, true);
315  }
316
317  @Test
318  public void testIsolateMetaAndRandomRegionOnTheMetaServer() throws Exception {
319    final TableName tableName = TableName.valueOf(testMethodName);
320    loadDummyDataInTable(tableName);
321    ServerName metaServerSource = findMetaRSLocation();
322    ServerName randomSeverRegion = findSourceServerName(tableName);
323    regionIsolationOperation(randomSeverRegion, metaServerSource, 2, true);
324  }
325
326  @Test
327  public void testIsolateMetaAndRandomRegionOnTheRandomServer() throws Exception {
328    final TableName tableName = TableName.valueOf(testMethodName);
329    loadDummyDataInTable(tableName);
330    ServerName randomSeverRegion = findSourceServerName(tableName);
331    regionIsolationOperation(randomSeverRegion, randomSeverRegion, 2, true);
332  }
333
334  private List<Put> createPuts(int count) {
335    List<Put> puts = new ArrayList<>();
336    for (int i = 0; i < count; i++) {
337      puts.add(new Put(Bytes.toBytes("rowkey_" + i)).addColumn(Bytes.toBytes(CF),
338        Bytes.toBytes("q1"), Bytes.toBytes("val_" + i)));
339    }
340    return puts;
341  }
342
343  public ServerName findMetaRSLocation() throws Exception {
344    List<HRegionLocation> result =
345      TEST_UTIL.getConnection().getRegionLocator(TableName.META_TABLE_NAME).getAllRegionLocations();
346    return result.get(0).getServerName();
347  }
348
349  public ServerName findSourceServerName(TableName tableName) throws Exception {
350    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
351    int numOfRS = cluster.getNumLiveRegionServers();
352    ServerName sourceServer = null;
353    for (int i = 0; i < numOfRS; i++) {
354      HRegionServer regionServer = cluster.getRegionServer(i);
355      List<HRegion> hRegions = regionServer.getRegions().stream()
356        .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
357        .collect(Collectors.toList());
358      if (hRegions.size() >= 2) {
359        sourceServer = regionServer.getServerName();
360        break;
361      }
362    }
363    if (sourceServer == null) {
364      throw new Exception(
365        "This shouldn't happen, No RS found with more than 2 regions of table : " + tableName);
366    }
367    return sourceServer;
368  }
369
370  public ServerName findDestinationServerName(ServerName sourceServerName) throws Exception {
371    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
372    ServerName destinationServerName = null;
373    int numOfRS = cluster.getNumLiveRegionServers();
374    for (int i = 0; i < numOfRS; i++) {
375      destinationServerName = cluster.getRegionServer(i).getServerName();
376      if (!destinationServerName.equals(sourceServerName)) {
377        break;
378      }
379    }
380    if (destinationServerName == null) {
381      throw new Exception("This shouldn't happen, No RS found which is different than source RS");
382    }
383    return destinationServerName;
384  }
385
386  public void regionIsolationOperation(ServerName sourceServerName,
387    ServerName destinationServerName, int numRegionsToIsolate, boolean isolateMetaAlso)
388    throws Exception {
389    final TableName tableName = TableName.valueOf(testMethodName);
390    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
391    Admin admin = TEST_UTIL.getAdmin();
392    HRegionServer sourceRS = cluster.getRegionServer(sourceServerName);
393    List<HRegion> hRegions = sourceRS.getRegions().stream()
394      .filter(hRegion -> hRegion.getRegionInfo().getTable().equals(tableName))
395      .collect(Collectors.toList());
396    List<String> listOfRegionIDsToIsolate = new ArrayList<>();
397    for (int i = 0; i < numRegionsToIsolate; i++) {
398      listOfRegionIDsToIsolate.add(hRegions.get(i).getRegionInfo().getEncodedName());
399    }
400
401    if (isolateMetaAlso) {
402      listOfRegionIDsToIsolate.remove(0);
403      listOfRegionIDsToIsolate.add(RegionInfoBuilder.FIRST_META_REGIONINFO.getEncodedName());
404    }
405
406    HRegionServer destinationRS = cluster.getRegionServer(destinationServerName);
407    String destinationRSName = destinationRS.getServerName().getAddress().toString();
408    RegionMover.RegionMoverBuilder rmBuilder =
409      new RegionMover.RegionMoverBuilder(destinationRSName, TEST_UTIL.getConfiguration()).ack(true)
410        .maxthreads(8).isolateRegionIdArray(listOfRegionIDsToIsolate);
411    try (RegionMover rm = rmBuilder.build()) {
412      LOG.debug("Unloading {} except regions: {}", destinationRS.getServerName(),
413        listOfRegionIDsToIsolate);
414      rm.isolateRegions();
415      assertEquals(numRegionsToIsolate, destinationRS.getNumberOfOnlineRegions());
416      List<HRegion> onlineRegions = destinationRS.getRegions();
417      for (int i = 0; i < numRegionsToIsolate; i++) {
418        assertTrue(
419          listOfRegionIDsToIsolate.contains(onlineRegions.get(i).getRegionInfo().getEncodedName()));
420      }
421      LOG.debug("Successfully Isolated {} regions: {} on {}", listOfRegionIDsToIsolate.size(),
422        listOfRegionIDsToIsolate, destinationRS.getServerName());
423    } finally {
424      admin.recommissionRegionServer(destinationRS.getServerName(), null);
425    }
426  }
427}