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.regionserver;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023import static org.junit.jupiter.api.Assertions.fail;
024
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.List;
029import java.util.Objects;
030import java.util.concurrent.ThreadLocalRandom;
031import java.util.concurrent.atomic.AtomicBoolean;
032import java.util.stream.Collectors;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.fs.FileSystem;
035import org.apache.hadoop.fs.Path;
036import org.apache.hadoop.hbase.CatalogFamilyFormat;
037import org.apache.hadoop.hbase.HBaseTestingUtil;
038import org.apache.hadoop.hbase.MetaTableAccessor;
039import org.apache.hadoop.hbase.ServerName;
040import org.apache.hadoop.hbase.SingleProcessHBaseCluster;
041import org.apache.hadoop.hbase.StartTestingClusterOption;
042import org.apache.hadoop.hbase.TableName;
043import org.apache.hadoop.hbase.UnknownRegionException;
044import org.apache.hadoop.hbase.client.Admin;
045import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
046import org.apache.hadoop.hbase.client.DoNotRetryRegionException;
047import org.apache.hadoop.hbase.client.Put;
048import org.apache.hadoop.hbase.client.RegionInfo;
049import org.apache.hadoop.hbase.client.RegionReplicaUtil;
050import org.apache.hadoop.hbase.client.Result;
051import org.apache.hadoop.hbase.client.ResultScanner;
052import org.apache.hadoop.hbase.client.Scan;
053import org.apache.hadoop.hbase.client.Table;
054import org.apache.hadoop.hbase.client.TableDescriptor;
055import org.apache.hadoop.hbase.exceptions.MergeRegionException;
056import org.apache.hadoop.hbase.master.HMaster;
057import org.apache.hadoop.hbase.master.MasterRpcServices;
058import org.apache.hadoop.hbase.master.RegionState;
059import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
060import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
061import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
062import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
063import org.apache.hadoop.hbase.testclassification.LargeTests;
064import org.apache.hadoop.hbase.testclassification.RegionServerTests;
065import org.apache.hadoop.hbase.util.Bytes;
066import org.apache.hadoop.hbase.util.CommonFSUtils;
067import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
068import org.apache.hadoop.hbase.util.FutureUtils;
069import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
070import org.apache.hadoop.hbase.util.Pair;
071import org.apache.hadoop.hbase.util.PairOfSameType;
072import org.apache.hadoop.hbase.util.Threads;
073import org.apache.hadoop.util.StringUtils;
074import org.apache.zookeeper.KeeperException;
075import org.junit.jupiter.api.AfterAll;
076import org.junit.jupiter.api.BeforeAll;
077import org.junit.jupiter.api.BeforeEach;
078import org.junit.jupiter.api.Tag;
079import org.junit.jupiter.api.Test;
080import org.junit.jupiter.api.TestInfo;
081import org.slf4j.Logger;
082import org.slf4j.LoggerFactory;
083
084import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
085import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
086import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
087
088import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionStateTransition.TransitionCode;
089import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.ReportRegionStateTransitionRequest;
090import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.ReportRegionStateTransitionResponse;
091
092@Tag(RegionServerTests.TAG)
093@Tag(LargeTests.TAG)
094public class TestRegionMergeTransactionOnCluster {
095
096  private static final Logger LOG =
097    LoggerFactory.getLogger(TestRegionMergeTransactionOnCluster.class);
098
099  private String methodName;
100
101  private static final int NB_SERVERS = 3;
102
103  private static final byte[] FAMILYNAME = Bytes.toBytes("fam");
104  private static final byte[] QUALIFIER = Bytes.toBytes("q");
105
106  private static byte[] ROW = Bytes.toBytes("testRow");
107  private static final int INITIAL_REGION_NUM = 10;
108  private static final int ROWSIZE = 200;
109  private static byte[][] ROWS = makeN(ROW, ROWSIZE);
110
111  private static int waitTime = 60 * 1000;
112
113  static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
114
115  private static HMaster MASTER;
116  private static Admin ADMIN;
117
118  @BeforeAll
119  public static void beforeAllTests() throws Exception {
120    // Start a cluster
121    StartTestingClusterOption option = StartTestingClusterOption.builder()
122      .masterClass(MyMaster.class).numRegionServers(NB_SERVERS).numDataNodes(NB_SERVERS).build();
123    TEST_UTIL.startMiniCluster(option);
124    SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
125    MASTER = cluster.getMaster();
126    MASTER.balanceSwitch(false);
127    ADMIN = TEST_UTIL.getConnection().getAdmin();
128  }
129
130  @BeforeEach
131  public void setUp(TestInfo testInfo) {
132    methodName = testInfo.getTestMethod().get().getName();
133  }
134
135  @AfterAll
136  public static void afterAllTests() throws Exception {
137    TEST_UTIL.shutdownMiniCluster();
138    if (ADMIN != null) {
139      ADMIN.close();
140    }
141  }
142
143  @Test
144  public void testWholesomeMerge() throws Exception {
145    LOG.info("Starting " + methodName);
146    final TableName tableName = TableName.valueOf(methodName);
147
148    try {
149      // Create table and load data.
150      Table table = createTableAndLoadData(MASTER, tableName);
151      // Merge 1st and 2nd region
152      mergeRegionsAndVerifyRegionNum(MASTER, tableName, 0, 1, INITIAL_REGION_NUM - 1);
153
154      // Merge 2nd and 3th region
155      PairOfSameType<RegionInfo> mergedRegions =
156        mergeRegionsAndVerifyRegionNum(MASTER, tableName, 1, 2, INITIAL_REGION_NUM - 2);
157
158      verifyRowCount(table, ROWSIZE);
159
160      // Randomly choose one of the two merged regions
161      RegionInfo hri = ThreadLocalRandom.current().nextBoolean()
162        ? mergedRegions.getFirst()
163        : mergedRegions.getSecond();
164      SingleProcessHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
165      AssignmentManager am = cluster.getMaster().getAssignmentManager();
166
167      // We should not be able to assign it again, but we are able to do it here. Assertions are
168      // poor here and missing that assign is possible here. Created HBASE-29692 for resolving this.
169      am.assign(hri);
170      assertFalse(am.getRegionStates().getRegionStateNode(hri).isTransitionScheduled(),
171        "Merged region can't be assigned");
172
173      // We should not be able to unassign it either
174      am.unassign(hri);
175      assertFalse(am.getRegionStates().getRegionStateNode(hri).isTransitionScheduled(),
176        "Merged region can't be unassigned");
177
178      table.close();
179    } finally {
180      TEST_UTIL.deleteTable(tableName);
181    }
182  }
183
184  /**
185   * Not really restarting the master. Simulate it by clear of new region state since it is not
186   * persisted, will be lost after master restarts.
187   */
188  @Test
189  public void testMergeAndRestartingMaster() throws Exception {
190    final TableName tableName = TableName.valueOf(methodName);
191
192    try {
193      // Create table and load data.
194      Table table = createTableAndLoadData(MASTER, tableName);
195
196      try {
197        MyMasterRpcServices.enabled.set(true);
198
199        // Merge 1st and 2nd region
200        mergeRegionsAndVerifyRegionNum(MASTER, tableName, 0, 1, INITIAL_REGION_NUM - 1);
201      } finally {
202        MyMasterRpcServices.enabled.set(false);
203      }
204
205      table.close();
206    } finally {
207      TEST_UTIL.deleteTable(tableName);
208    }
209  }
210
211  @Test
212  public void testCleanMergeReference() throws Exception {
213    LOG.info("Starting " + methodName);
214    ADMIN.catalogJanitorSwitch(false);
215    final TableName tableName = TableName.valueOf(methodName);
216    try {
217      // Create table and load data.
218      Table table = createTableAndLoadData(MASTER, tableName);
219      // Merge 1st and 2nd region
220      mergeRegionsAndVerifyRegionNum(MASTER, tableName, 0, 1, INITIAL_REGION_NUM - 1);
221      verifyRowCount(table, ROWSIZE);
222      table.close();
223
224      List<Pair<RegionInfo, ServerName>> tableRegions =
225        MetaTableAccessor.getTableRegionsAndLocations(MASTER.getConnection(), tableName);
226      RegionInfo mergedRegionInfo = tableRegions.get(0).getFirst();
227      TableDescriptor tableDescriptor = MASTER.getTableDescriptors().get(tableName);
228      Result mergedRegionResult =
229        MetaTableAccessor.getRegionResult(MASTER.getConnection(), mergedRegionInfo);
230
231      // contains merge reference in META
232      assertTrue(CatalogFamilyFormat.hasMergeRegions(mergedRegionResult.rawCells()));
233
234      // merging regions' directory are in the file system all the same
235      List<RegionInfo> p = CatalogFamilyFormat.getMergeRegions(mergedRegionResult.rawCells());
236      RegionInfo regionA = p.get(0);
237      RegionInfo regionB = p.get(1);
238      FileSystem fs = MASTER.getMasterFileSystem().getFileSystem();
239      Path rootDir = MASTER.getMasterFileSystem().getRootDir();
240
241      Path tabledir = CommonFSUtils.getTableDir(rootDir, mergedRegionInfo.getTable());
242      Path regionAdir = new Path(tabledir, regionA.getEncodedName());
243      Path regionBdir = new Path(tabledir, regionB.getEncodedName());
244      assertTrue(fs.exists(regionAdir));
245      assertTrue(fs.exists(regionBdir));
246
247      ColumnFamilyDescriptor[] columnFamilies = tableDescriptor.getColumnFamilies();
248      HRegionFileSystem hrfs =
249        new HRegionFileSystem(TEST_UTIL.getConfiguration(), fs, tabledir, mergedRegionInfo);
250      int count = 0;
251      for (ColumnFamilyDescriptor colFamily : columnFamilies) {
252        StoreFileTracker sft = StoreFileTrackerFactory.create(TEST_UTIL.getConfiguration(),
253          tableDescriptor, colFamily, hrfs, false);
254        count += sft.load().size();
255      }
256      ADMIN.compactRegion(mergedRegionInfo.getRegionName());
257      // clean up the merged region store files
258      // wait until merged region have reference file
259      long timeout = EnvironmentEdgeManager.currentTime() + waitTime;
260      int newcount = 0;
261      while (EnvironmentEdgeManager.currentTime() < timeout) {
262        for (ColumnFamilyDescriptor colFamily : columnFamilies) {
263          StoreFileTracker sft = StoreFileTrackerFactory.create(TEST_UTIL.getConfiguration(),
264            tableDescriptor, colFamily, hrfs, false);
265          newcount += sft.load().size();
266        }
267        if (newcount > count) {
268          break;
269        }
270        Thread.sleep(50);
271      }
272      assertTrue(newcount > count);
273      List<RegionServerThread> regionServerThreads =
274        TEST_UTIL.getHBaseCluster().getRegionServerThreads();
275      for (RegionServerThread rs : regionServerThreads) {
276        CompactedHFilesDischarger cleaner =
277          new CompactedHFilesDischarger(100, null, rs.getRegionServer(), false);
278        cleaner.chore();
279        Thread.sleep(1000);
280      }
281      while (EnvironmentEdgeManager.currentTime() < timeout) {
282        int newcount1 = 0;
283        for (ColumnFamilyDescriptor colFamily : columnFamilies) {
284          StoreFileTracker sft = StoreFileTrackerFactory.create(TEST_UTIL.getConfiguration(),
285            tableDescriptor, colFamily, hrfs, false);
286          newcount1 += sft.load().size();
287        }
288        if (newcount1 <= 1) {
289          break;
290        }
291        Thread.sleep(50);
292      }
293      // run CatalogJanitor to clean merge references in hbase:meta and archive the
294      // files of merging regions
295      int cleaned = 0;
296      while (cleaned == 0) {
297        cleaned = ADMIN.runCatalogJanitor();
298        LOG.debug("catalog janitor returned " + cleaned);
299        Thread.sleep(50);
300        // Cleanup is async so wait till all procedures are done running.
301        ProcedureTestingUtility.waitNoProcedureRunning(
302          TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor());
303      }
304      // We used to check for existence of region in fs but sometimes the region dir was
305      // cleaned up by the time we got here making the test sometimes flakey.
306      assertTrue(cleaned > 0);
307
308      // Wait around a bit to give stuff a chance to complete.
309      while (true) {
310        mergedRegionResult =
311          MetaTableAccessor.getRegionResult(TEST_UTIL.getConnection(), mergedRegionInfo);
312        if (CatalogFamilyFormat.hasMergeRegions(mergedRegionResult.rawCells())) {
313          LOG.info("Waiting on cleanup of merge columns {}",
314            Arrays.asList(mergedRegionResult.rawCells()).stream().map(c -> c.toString())
315              .collect(Collectors.joining(",")));
316          Threads.sleep(50);
317        } else {
318          break;
319        }
320      }
321      assertFalse(CatalogFamilyFormat.hasMergeRegions(mergedRegionResult.rawCells()));
322    } finally {
323      ADMIN.catalogJanitorSwitch(true);
324      TEST_UTIL.deleteTable(tableName);
325    }
326  }
327
328  /**
329   * This test tests 1, merging region not online; 2, merging same two regions; 3, merging unknown
330   * regions. They are in one test case so that we don't have to create many tables, and these tests
331   * are simple.
332   */
333  @Test
334  public void testMerge() throws Exception {
335    LOG.info("Starting " + methodName);
336    final TableName tableName = TableName.valueOf(methodName);
337    final Admin admin = TEST_UTIL.getAdmin();
338
339    try {
340      // Create table and load data.
341      Table table = createTableAndLoadData(MASTER, tableName);
342      AssignmentManager am = MASTER.getAssignmentManager();
343      List<RegionInfo> regions = am.getRegionStates().getRegionsOfTable(tableName);
344      // Fake offline one region
345      RegionInfo a = regions.get(0);
346      RegionInfo b = regions.get(1);
347      am.unassign(b);
348      am.offlineRegion(b);
349      try {
350        // Merge offline region. Region a is offline here
351        FutureUtils.get(
352          admin.mergeRegionsAsync(a.getEncodedNameAsBytes(), b.getEncodedNameAsBytes(), false));
353        fail("Offline regions should not be able to merge");
354      } catch (DoNotRetryRegionException ie) {
355        System.out.println(ie);
356        assertTrue(ie instanceof MergeRegionException);
357      }
358
359      try {
360        // Merge the same region: b and b.
361        FutureUtils
362          .get(admin.mergeRegionsAsync(b.getEncodedNameAsBytes(), b.getEncodedNameAsBytes(), true));
363        fail("A region should not be able to merge with itself, even forcfully");
364      } catch (IOException ie) {
365        assertTrue(StringUtils.stringifyException(ie).contains("region to itself")
366          && ie instanceof MergeRegionException, "Exception should mention regions not online");
367      }
368
369      try {
370        // Merge unknown regions
371        FutureUtils.get(admin.mergeRegionsAsync(Bytes.toBytes("-f1"), Bytes.toBytes("-f2"), true));
372        fail("Unknown region could not be merged");
373      } catch (IOException ie) {
374        assertTrue(ie instanceof UnknownRegionException, "UnknownRegionException should be thrown");
375      }
376      table.close();
377    } finally {
378      TEST_UTIL.deleteTable(tableName);
379    }
380  }
381
382  @Test
383  public void testMergeWithReplicas() throws Exception {
384    final TableName tableName = TableName.valueOf(methodName);
385    try {
386      // Create table and load data.
387      Table table = createTableAndLoadData(MASTER, tableName, 5, 2);
388      List<Pair<RegionInfo, ServerName>> initialRegionToServers =
389        MetaTableAccessor.getTableRegionsAndLocations(TEST_UTIL.getConnection(), tableName);
390      // Merge 1st and 2nd region
391      PairOfSameType<RegionInfo> mergedRegions =
392        mergeRegionsAndVerifyRegionNum(MASTER, tableName, 0, 2, 5 * 2 - 2);
393      List<Pair<RegionInfo, ServerName>> currentRegionToServers =
394        MetaTableAccessor.getTableRegionsAndLocations(TEST_UTIL.getConnection(), tableName);
395      List<RegionInfo> initialRegions = new ArrayList<>();
396      for (Pair<RegionInfo, ServerName> p : initialRegionToServers) {
397        initialRegions.add(p.getFirst());
398      }
399      List<RegionInfo> currentRegions = new ArrayList<>();
400      for (Pair<RegionInfo, ServerName> p : currentRegionToServers) {
401        currentRegions.add(p.getFirst());
402      }
403      // this is the first region
404      assertTrue(initialRegions.contains(mergedRegions.getFirst()));
405      // this is the replica of the first region
406      assertTrue(initialRegions
407        .contains(RegionReplicaUtil.getRegionInfoForReplica(mergedRegions.getFirst(), 1)));
408      // this is the second region
409      assertTrue(initialRegions.contains(mergedRegions.getSecond()));
410      // this is the replica of the second region
411      assertTrue(initialRegions
412        .contains(RegionReplicaUtil.getRegionInfoForReplica(mergedRegions.getSecond(), 1)));
413      // this is the new region
414      assertTrue(!initialRegions.contains(currentRegions.get(0)));
415      // replica of the new region
416      assertTrue(!initialRegions
417        .contains(RegionReplicaUtil.getRegionInfoForReplica(currentRegions.get(0), 1)));
418      // replica of the new region
419      assertTrue(currentRegions
420        .contains(RegionReplicaUtil.getRegionInfoForReplica(currentRegions.get(0), 1)));
421      // replica of the merged region
422      assertTrue(!currentRegions
423        .contains(RegionReplicaUtil.getRegionInfoForReplica(mergedRegions.getFirst(), 1)));
424      // replica of the merged region
425      assertTrue(!currentRegions
426        .contains(RegionReplicaUtil.getRegionInfoForReplica(mergedRegions.getSecond(), 1)));
427      table.close();
428    } finally {
429      TEST_UTIL.deleteTable(tableName);
430    }
431  }
432
433  private PairOfSameType<RegionInfo> mergeRegionsAndVerifyRegionNum(HMaster master,
434    TableName tablename, int regionAnum, int regionBnum, int expectedRegionNum) throws Exception {
435    PairOfSameType<RegionInfo> mergedRegions =
436      requestMergeRegion(master, tablename, regionAnum, regionBnum);
437    waitAndVerifyRegionNum(master, tablename, expectedRegionNum);
438    return mergedRegions;
439  }
440
441  private PairOfSameType<RegionInfo> requestMergeRegion(HMaster master, TableName tablename,
442    int regionAnum, int regionBnum) throws Exception {
443    List<Pair<RegionInfo, ServerName>> tableRegions =
444      MetaTableAccessor.getTableRegionsAndLocations(TEST_UTIL.getConnection(), tablename);
445    RegionInfo regionA = tableRegions.get(regionAnum).getFirst();
446    RegionInfo regionB = tableRegions.get(regionBnum).getFirst();
447    ADMIN.mergeRegionsAsync(regionA.getEncodedNameAsBytes(), regionB.getEncodedNameAsBytes(),
448      false);
449    return new PairOfSameType<>(regionA, regionB);
450  }
451
452  private void waitAndVerifyRegionNum(HMaster master, TableName tablename, int expectedRegionNum)
453    throws Exception {
454    List<Pair<RegionInfo, ServerName>> tableRegionsInMeta;
455    List<RegionInfo> tableRegionsInMaster;
456    long timeout = EnvironmentEdgeManager.currentTime() + waitTime;
457    while (EnvironmentEdgeManager.currentTime() < timeout) {
458      tableRegionsInMeta =
459        MetaTableAccessor.getTableRegionsAndLocations(TEST_UTIL.getConnection(), tablename);
460      tableRegionsInMaster =
461        master.getAssignmentManager().getRegionStates().getRegionsOfTable(tablename);
462      LOG.info(Objects.toString(tableRegionsInMaster));
463      LOG.info(Objects.toString(tableRegionsInMeta));
464      int tableRegionsInMetaSize = tableRegionsInMeta.size();
465      int tableRegionsInMasterSize = tableRegionsInMaster.size();
466      if (
467        tableRegionsInMetaSize == expectedRegionNum && tableRegionsInMasterSize == expectedRegionNum
468      ) {
469        break;
470      }
471      Thread.sleep(250);
472    }
473
474    tableRegionsInMeta =
475      MetaTableAccessor.getTableRegionsAndLocations(TEST_UTIL.getConnection(), tablename);
476    LOG.info("Regions after merge:" + Joiner.on(',').join(tableRegionsInMeta));
477    assertEquals(expectedRegionNum, tableRegionsInMeta.size());
478  }
479
480  private Table createTableAndLoadData(HMaster master, TableName tablename) throws Exception {
481    return createTableAndLoadData(master, tablename, INITIAL_REGION_NUM, 1);
482  }
483
484  private Table createTableAndLoadData(HMaster master, TableName tablename, int numRegions,
485    int replication) throws Exception {
486    assertTrue(ROWSIZE > numRegions, "ROWSIZE must > numregions:" + numRegions);
487    byte[][] splitRows = new byte[numRegions - 1][];
488    for (int i = 0; i < splitRows.length; i++) {
489      splitRows[i] = ROWS[(i + 1) * ROWSIZE / numRegions];
490    }
491
492    Table table = TEST_UTIL.createTable(tablename, FAMILYNAME, splitRows);
493    LOG.info("Created " + table.getName());
494    if (replication > 1) {
495      HBaseTestingUtil.setReplicas(ADMIN, tablename, replication);
496      LOG.info("Set replication of " + replication + " on " + table.getName());
497    }
498    loadData(table);
499    LOG.info("Loaded " + table.getName());
500    verifyRowCount(table, ROWSIZE);
501    LOG.info("Verified " + table.getName());
502
503    List<Pair<RegionInfo, ServerName>> tableRegions;
504    TEST_UTIL.waitUntilAllRegionsAssigned(tablename);
505    LOG.info("All regions assigned for table - " + table.getName());
506    tableRegions =
507      MetaTableAccessor.getTableRegionsAndLocations(TEST_UTIL.getConnection(), tablename);
508    assertEquals(numRegions * replication, tableRegions.size(),
509      "Wrong number of regions in table " + tablename);
510    LOG.info(tableRegions.size() + "Regions after load: " + Joiner.on(',').join(tableRegions));
511    assertEquals(numRegions * replication, tableRegions.size());
512    return table;
513  }
514
515  private static byte[][] makeN(byte[] base, int n) {
516    byte[][] ret = new byte[n][];
517    for (int i = 0; i < n; i++) {
518      ret[i] = Bytes.add(base, Bytes.toBytes(String.format("%04d", i)));
519    }
520    return ret;
521  }
522
523  private void loadData(Table table) throws IOException {
524    for (int i = 0; i < ROWSIZE; i++) {
525      Put put = new Put(ROWS[i]);
526      put.addColumn(FAMILYNAME, QUALIFIER, Bytes.toBytes(i));
527      table.put(put);
528    }
529  }
530
531  private void verifyRowCount(Table table, int expectedRegionNum) throws IOException {
532    ResultScanner scanner = table.getScanner(new Scan());
533    int rowCount = 0;
534    while (scanner.next() != null) {
535      rowCount++;
536    }
537    assertEquals(expectedRegionNum, rowCount);
538    scanner.close();
539  }
540
541  // Make it public so that JVMClusterUtil can access it.
542  public static class MyMaster extends HMaster {
543    public MyMaster(Configuration conf) throws IOException, KeeperException, InterruptedException {
544      super(conf);
545    }
546
547    @Override
548    protected MasterRpcServices createRpcServices() throws IOException {
549      return new MyMasterRpcServices(this);
550    }
551  }
552
553  static class MyMasterRpcServices extends MasterRpcServices {
554    static AtomicBoolean enabled = new AtomicBoolean(false);
555
556    private HMaster myMaster;
557
558    public MyMasterRpcServices(HMaster master) throws IOException {
559      super(master);
560      myMaster = master;
561    }
562
563    @Override
564    public ReportRegionStateTransitionResponse reportRegionStateTransition(RpcController c,
565      ReportRegionStateTransitionRequest req) throws ServiceException {
566      ReportRegionStateTransitionResponse resp = super.reportRegionStateTransition(c, req);
567      if (
568        enabled.get() && req.getTransition(0).getTransitionCode() == TransitionCode.READY_TO_MERGE
569          && !resp.hasErrorMessage()
570      ) {
571        AssignmentManager am = myMaster.getAssignmentManager();
572        for (RegionState regionState : am.getRegionsStateInTransition()) {
573          // Find the merging_new region and remove it
574          if (regionState.isMergingNew()) {
575            am.getRegionStates().deleteRegion(regionState.getRegion());
576          }
577        }
578      }
579      return resp;
580    }
581  }
582}