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.client;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertThrows;
024import static org.junit.Assert.assertTrue;
025import static org.junit.Assert.fail;
026
027import java.io.IOException;
028import java.util.ArrayList;
029import java.util.List;
030import java.util.concurrent.ExecutionException;
031import java.util.concurrent.TimeUnit;
032import java.util.concurrent.atomic.AtomicInteger;
033import org.apache.hadoop.hbase.HBaseClassTestRule;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.HRegionLocation;
036import org.apache.hadoop.hbase.MetaTableAccessor;
037import org.apache.hadoop.hbase.ServerName;
038import org.apache.hadoop.hbase.TableName;
039import org.apache.hadoop.hbase.TableNotFoundException;
040import org.apache.hadoop.hbase.exceptions.MergeRegionException;
041import org.apache.hadoop.hbase.master.HMaster;
042import org.apache.hadoop.hbase.master.janitor.CatalogJanitor;
043import org.apache.hadoop.hbase.regionserver.DisabledRegionSplitPolicy;
044import org.apache.hadoop.hbase.regionserver.HRegion;
045import org.apache.hadoop.hbase.regionserver.HRegionServer;
046import org.apache.hadoop.hbase.regionserver.HStore;
047import org.apache.hadoop.hbase.regionserver.HStoreFile;
048import org.apache.hadoop.hbase.regionserver.Region;
049import org.apache.hadoop.hbase.testclassification.ClientTests;
050import org.apache.hadoop.hbase.testclassification.LargeTests;
051import org.apache.hadoop.hbase.util.Bytes;
052import org.apache.hadoop.hbase.util.CommonFSUtils;
053import org.apache.hadoop.hbase.util.Pair;
054import org.apache.hadoop.hbase.util.Threads;
055import org.junit.ClassRule;
056import org.junit.Test;
057import org.junit.experimental.categories.Category;
058import org.slf4j.Logger;
059import org.slf4j.LoggerFactory;
060
061import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
062import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MergeTableRegionsRequest;
063
064/**
065 * Class to test HBaseAdmin. Spins up the minicluster once at test start and then takes it down
066 * afterward. Add any testing of HBaseAdmin functionality here.
067 */
068@Category({ LargeTests.class, ClientTests.class })
069public class TestAdmin1 extends TestAdminBase {
070
071  @ClassRule
072  public static final HBaseClassTestRule CLASS_RULE = HBaseClassTestRule.forClass(TestAdmin1.class);
073
074  private static final Logger LOG = LoggerFactory.getLogger(TestAdmin1.class);
075
076  @Test
077  public void testCompactRegionWithTableName() throws Exception {
078    TableName tableName = TableName.valueOf(name.getMethodName());
079    try {
080      TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName)
081        .setColumnFamily(ColumnFamilyDescriptorBuilder.of("fam1")).build();
082      ADMIN.createTable(htd);
083      Region metaRegion = null;
084      for (int i = 0; i < NB_SERVERS; i++) {
085        HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(i);
086        List<HRegion> onlineRegions = rs.getRegions(TableName.META_TABLE_NAME);
087        if (!onlineRegions.isEmpty()) {
088          metaRegion = onlineRegions.get(0);
089          break;
090        }
091      }
092
093      long metaReadCountBeforeCompact = metaRegion.getReadRequestsCount();
094      try {
095        ADMIN.majorCompactRegion(tableName.getName());
096      } catch (IllegalArgumentException iae) {
097        LOG.info("This is expected");
098      }
099      assertEquals(metaReadCountBeforeCompact, metaRegion.getReadRequestsCount());
100    } finally {
101      ADMIN.disableTable(tableName);
102      ADMIN.deleteTable(tableName);
103    }
104  }
105
106  @Test
107  public void testSplitFlushCompactUnknownTable() throws InterruptedException {
108    final TableName unknowntable = TableName.valueOf(name.getMethodName());
109    Exception exception = null;
110    try {
111      ADMIN.compact(unknowntable);
112    } catch (IOException e) {
113      exception = e;
114    }
115    assertTrue(exception instanceof TableNotFoundException);
116
117    exception = null;
118    try {
119      ADMIN.flush(unknowntable);
120    } catch (IOException e) {
121      exception = e;
122    }
123    assertTrue(exception instanceof TableNotFoundException);
124
125    exception = null;
126    try {
127      ADMIN.split(unknowntable);
128    } catch (IOException e) {
129      exception = e;
130    }
131    assertTrue(exception instanceof TableNotFoundException);
132  }
133
134  @Test
135  public void testCompactATableWithSuperLongTableName() throws Exception {
136    TableName tableName = TableName.valueOf(name.getMethodName());
137    TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName)
138      .setColumnFamily(ColumnFamilyDescriptorBuilder.of("fam1")).build();
139    try {
140      ADMIN.createTable(htd);
141      assertThrows(IllegalArgumentException.class,
142        () -> ADMIN.majorCompactRegion(tableName.getName()));
143
144      assertThrows(IllegalArgumentException.class,
145        () -> ADMIN.majorCompactRegion(Bytes.toBytes("abcd")));
146    } finally {
147      ADMIN.disableTable(tableName);
148      ADMIN.deleteTable(tableName);
149    }
150  }
151
152  @Test
153  public void testCompactionTimestamps() throws Exception {
154    TableName tableName = TableName.valueOf(name.getMethodName());
155    TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName)
156      .setColumnFamily(ColumnFamilyDescriptorBuilder.of("fam1")).build();
157    ADMIN.createTable(htd);
158    Table table = TEST_UTIL.getConnection().getTable(htd.getTableName());
159    long ts = ADMIN.getLastMajorCompactionTimestamp(tableName);
160    assertEquals(0, ts);
161    Put p = new Put(Bytes.toBytes("row1"));
162    p.addColumn(Bytes.toBytes("fam1"), Bytes.toBytes("fam1"), Bytes.toBytes("fam1"));
163    table.put(p);
164    ts = ADMIN.getLastMajorCompactionTimestamp(tableName);
165    // no files written -> no data
166    assertEquals(0, ts);
167
168    ADMIN.flush(tableName);
169    ts = ADMIN.getLastMajorCompactionTimestamp(tableName);
170    // still 0, we flushed a file, but no major compaction happened
171    assertEquals(0, ts);
172
173    byte[] regionName;
174    try (RegionLocator l = TEST_UTIL.getConnection().getRegionLocator(tableName)) {
175      regionName = l.getAllRegionLocations().get(0).getRegion().getRegionName();
176    }
177    long ts1 = ADMIN.getLastMajorCompactionTimestampForRegion(regionName);
178    assertEquals(ts, ts1);
179    p = new Put(Bytes.toBytes("row2"));
180    p.addColumn(Bytes.toBytes("fam1"), Bytes.toBytes("fam1"), Bytes.toBytes("fam1"));
181    table.put(p);
182    ADMIN.flush(tableName);
183    ts = ADMIN.getLastMajorCompactionTimestamp(tableName);
184    // make sure the region API returns the same value, as the old file is still around
185    assertEquals(ts1, ts);
186
187    TEST_UTIL.compact(tableName, true);
188    table.put(p);
189    // forces a wait for the compaction
190    ADMIN.flush(tableName);
191    ts = ADMIN.getLastMajorCompactionTimestamp(tableName);
192    // after a compaction our earliest timestamp will have progressed forward
193    assertTrue(ts > ts1);
194
195    // region api still the same
196    ts1 = ADMIN.getLastMajorCompactionTimestampForRegion(regionName);
197    assertEquals(ts, ts1);
198    table.put(p);
199    ADMIN.flush(tableName);
200    ts = ADMIN.getLastMajorCompactionTimestamp(tableName);
201    assertEquals(ts, ts1);
202    table.close();
203  }
204
205  @Test(expected = IllegalArgumentException.class)
206  public void testColumnValidName() {
207    ColumnFamilyDescriptorBuilder.of("\\test\\abc");
208  }
209
210  @Test
211  public void testTableExist() throws IOException {
212    final TableName table = TableName.valueOf(name.getMethodName());
213    boolean exist;
214    exist = ADMIN.tableExists(table);
215    assertEquals(false, exist);
216    TEST_UTIL.createTable(table, HConstants.CATALOG_FAMILY);
217    exist = ADMIN.tableExists(table);
218    assertEquals(true, exist);
219  }
220
221  /**
222   * Tests forcing split from client and having scanners successfully ride over split.
223   */
224  @Test
225  public void testForceSplit() throws Exception {
226    byte[][] familyNames = new byte[][] { Bytes.toBytes("cf") };
227    int[] rowCounts = new int[] { 6000 };
228    int numVersions = ColumnFamilyDescriptorBuilder.DEFAULT_MAX_VERSIONS;
229    int blockSize = 256;
230    splitTest(null, familyNames, rowCounts, numVersions, blockSize, true);
231
232    byte[] splitKey = Bytes.toBytes(3500);
233    splitTest(splitKey, familyNames, rowCounts, numVersions, blockSize, true);
234    // test regionSplitSync
235    splitTest(splitKey, familyNames, rowCounts, numVersions, blockSize, false);
236  }
237
238  /**
239   * Multi-family scenario. Tests forcing split from client and having scanners successfully ride
240   * over split.
241   */
242  @Test
243  public void testForceSplitMultiFamily() throws Exception {
244    int numVersions = ColumnFamilyDescriptorBuilder.DEFAULT_MAX_VERSIONS;
245
246    // use small HFile block size so that we can have lots of blocks in HFile
247    // Otherwise, if there is only one block,
248    // HFileBlockIndex.midKey()'s value == startKey
249    int blockSize = 256;
250    byte[][] familyNames = new byte[][] { Bytes.toBytes("cf1"), Bytes.toBytes("cf2") };
251
252    // one of the column families isn't splittable
253    int[] rowCounts = new int[] { 6000, 1 };
254    splitTest(null, familyNames, rowCounts, numVersions, blockSize, true);
255
256    rowCounts = new int[] { 1, 6000 };
257    splitTest(null, familyNames, rowCounts, numVersions, blockSize, true);
258
259    // one column family has much smaller data than the other
260    // the split key should be based on the largest column family
261    rowCounts = new int[] { 6000, 300 };
262    splitTest(null, familyNames, rowCounts, numVersions, blockSize, true);
263
264    rowCounts = new int[] { 300, 6000 };
265    splitTest(null, familyNames, rowCounts, numVersions, blockSize, true);
266  }
267
268  private int count(ResultScanner scanner) throws IOException {
269    int rows = 0;
270    while (scanner.next() != null) {
271      rows++;
272    }
273    return rows;
274  }
275
276  private void splitTest(byte[] splitPoint, byte[][] familyNames, int[] rowCounts, int numVersions,
277    int blockSize, boolean async) throws Exception {
278    TableName tableName = TableName.valueOf("testForceSplit");
279    StringBuilder sb = new StringBuilder();
280    // Add tail to String so can see better in logs where a test is running.
281    for (int i = 0; i < rowCounts.length; i++) {
282      sb.append("_").append(Integer.toString(rowCounts[i]));
283    }
284    assertFalse(ADMIN.tableExists(tableName));
285    try (final Table table = TEST_UTIL.createTable(tableName, familyNames, numVersions, blockSize);
286      final RegionLocator locator = TEST_UTIL.getConnection().getRegionLocator(tableName)) {
287
288      int rowCount = 0;
289      byte[] q = new byte[0];
290
291      // insert rows into column families. The number of rows that have values
292      // in a specific column family is decided by rowCounts[familyIndex]
293      for (int index = 0; index < familyNames.length; index++) {
294        ArrayList<Put> puts = new ArrayList<>(rowCounts[index]);
295        for (int i = 0; i < rowCounts[index]; i++) {
296          byte[] k = Bytes.toBytes(i);
297          Put put = new Put(k);
298          put.addColumn(familyNames[index], q, k);
299          puts.add(put);
300        }
301        table.put(puts);
302
303        if (rowCount < rowCounts[index]) {
304          rowCount = rowCounts[index];
305        }
306      }
307
308      // get the initial layout (should just be one region)
309      List<HRegionLocation> m = locator.getAllRegionLocations();
310      LOG.info("Initial regions (" + m.size() + "): " + m);
311      assertTrue(m.size() == 1);
312
313      // Verify row count
314      Scan scan = new Scan();
315      int rows;
316      try (ResultScanner scanner = table.getScanner(scan)) {
317        rows = count(scanner);
318      }
319      assertEquals(rowCount, rows);
320
321      // Have an outstanding scan going on to make sure we can scan over splits.
322      scan = new Scan();
323      try (ResultScanner scanner = table.getScanner(scan)) {
324        // Scan first row so we are into first region before split happens.
325        scanner.next();
326
327        // Split the table
328        if (async) {
329          ADMIN.split(tableName, splitPoint);
330          final AtomicInteger count = new AtomicInteger(0);
331          Thread t = new Thread("CheckForSplit") {
332            @Override
333            public void run() {
334              for (int i = 0; i < 45; i++) {
335                try {
336                  sleep(1000);
337                } catch (InterruptedException e) {
338                  continue;
339                }
340                // check again
341                List<HRegionLocation> regions = null;
342                try {
343                  regions = locator.getAllRegionLocations();
344                } catch (IOException e) {
345                  LOG.warn("get location failed", e);
346                }
347                if (regions == null) {
348                  continue;
349                }
350                count.set(regions.size());
351                if (count.get() >= 2) {
352                  LOG.info("Found: " + regions);
353                  break;
354                }
355                LOG.debug("Cycle waiting on split");
356              }
357              LOG.debug("CheckForSplit thread exited, current region count: " + count.get());
358            }
359          };
360          t.setPriority(Thread.NORM_PRIORITY - 2);
361          t.start();
362          t.join();
363        } else {
364          // Sync split region, no need to create a thread to check
365          ADMIN.splitRegionAsync(m.get(0).getRegion().getRegionName(), splitPoint).get();
366        }
367        // Verify row count
368        rows = 1 + count(scanner); // We counted one row above.
369      }
370      assertEquals(rowCount, rows);
371
372      List<HRegionLocation> regions = null;
373      try {
374        regions = locator.getAllRegionLocations();
375      } catch (IOException e) {
376        e.printStackTrace();
377      }
378      assertEquals(2, regions.size());
379      if (splitPoint != null) {
380        // make sure the split point matches our explicit configuration
381        assertEquals(Bytes.toString(splitPoint),
382          Bytes.toString(regions.get(0).getRegion().getEndKey()));
383        assertEquals(Bytes.toString(splitPoint),
384          Bytes.toString(regions.get(1).getRegion().getStartKey()));
385        LOG.debug("Properly split on " + Bytes.toString(splitPoint));
386      } else {
387        if (familyNames.length > 1) {
388          int splitKey = Bytes.toInt(regions.get(0).getRegion().getEndKey());
389          // check if splitKey is based on the largest column family
390          // in terms of it store size
391          int deltaForLargestFamily = Math.abs(rowCount / 2 - splitKey);
392          LOG.debug("SplitKey=" + splitKey + "&deltaForLargestFamily=" + deltaForLargestFamily
393            + ", r=" + regions.get(0).getRegion());
394          for (int index = 0; index < familyNames.length; index++) {
395            int delta = Math.abs(rowCounts[index] / 2 - splitKey);
396            if (delta < deltaForLargestFamily) {
397              assertTrue("Delta " + delta + " for family " + index + " should be at least "
398                + "deltaForLargestFamily " + deltaForLargestFamily, false);
399            }
400          }
401        }
402      }
403      TEST_UTIL.deleteTable(tableName);
404    }
405  }
406
407  @Test
408  public void testSplitAndMergeWithReplicaTable() throws Exception {
409    // The test tries to directly split replica regions and directly merge replica regions. These
410    // are not allowed. The test validates that. Then the test does a valid split/merge of allowed
411    // regions.
412    // Set up a table with 3 regions and replication set to 3
413    TableName tableName = TableName.valueOf(name.getMethodName());
414    byte[] cf = Bytes.toBytes("f");
415    TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(3)
416      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(cf)).build();
417    byte[][] splitRows = new byte[2][];
418    splitRows[0] = new byte[] { (byte) '4' };
419    splitRows[1] = new byte[] { (byte) '7' };
420    TEST_UTIL.getAdmin().createTable(desc, splitRows);
421    List<HRegion> oldRegions;
422    do {
423      oldRegions = TEST_UTIL.getHBaseCluster().getRegions(tableName);
424      Thread.sleep(10);
425    } while (oldRegions.size() != 9); // 3 regions * 3 replicas
426    // write some data to the table
427    Table ht = TEST_UTIL.getConnection().getTable(tableName);
428    List<Put> puts = new ArrayList<>();
429    byte[] qualifier = Bytes.toBytes("c");
430    Put put = new Put(new byte[] { (byte) '1' });
431    put.addColumn(cf, qualifier, Bytes.toBytes("100"));
432    puts.add(put);
433    put = new Put(new byte[] { (byte) '6' });
434    put.addColumn(cf, qualifier, Bytes.toBytes("100"));
435    puts.add(put);
436    put = new Put(new byte[] { (byte) '8' });
437    put.addColumn(cf, qualifier, Bytes.toBytes("100"));
438    puts.add(put);
439    ht.put(puts);
440    ht.close();
441    List<Pair<RegionInfo, ServerName>> regions =
442      MetaTableAccessor.getTableRegionsAndLocations(TEST_UTIL.getConnection(), tableName);
443    boolean gotException = false;
444    // the element at index 1 would be a replica (since the metareader gives us ordered
445    // regions). Try splitting that region via the split API . Should fail
446    try {
447      TEST_UTIL.getAdmin().splitRegionAsync(regions.get(1).getFirst().getRegionName()).get();
448    } catch (IllegalArgumentException ex) {
449      gotException = true;
450    }
451    assertTrue(gotException);
452    gotException = false;
453    // the element at index 1 would be a replica (since the metareader gives us ordered
454    // regions). Try splitting that region via a different split API (the difference is
455    // this API goes direct to the regionserver skipping any checks in the admin). Should fail
456    try {
457      TEST_UTIL.getHBaseAdmin().splitRegionAsync(regions.get(1).getFirst(),
458        new byte[] { (byte) '1' });
459    } catch (IOException ex) {
460      gotException = true;
461    }
462    assertTrue(gotException);
463
464    gotException = false;
465    // testing Sync split operation
466    try {
467      TEST_UTIL.getAdmin()
468        .splitRegionAsync(regions.get(1).getFirst().getRegionName(), new byte[] { (byte) '1' })
469        .get();
470    } catch (IllegalArgumentException ex) {
471      gotException = true;
472    }
473    assertTrue(gotException);
474
475    gotException = false;
476    // Try merging a replica with another. Should fail.
477    try {
478      TEST_UTIL.getAdmin().mergeRegionsAsync(regions.get(1).getFirst().getEncodedNameAsBytes(),
479        regions.get(2).getFirst().getEncodedNameAsBytes(), true).get();
480    } catch (IllegalArgumentException m) {
481      gotException = true;
482    }
483    assertTrue(gotException);
484    // Try going to the master directly (that will skip the check in admin)
485    try {
486      byte[][] nameofRegionsToMerge = new byte[2][];
487      nameofRegionsToMerge[0] = regions.get(1).getFirst().getEncodedNameAsBytes();
488      nameofRegionsToMerge[1] = regions.get(2).getFirst().getEncodedNameAsBytes();
489      MergeTableRegionsRequest request = RequestConverter.buildMergeTableRegionsRequest(
490        nameofRegionsToMerge, true, HConstants.NO_NONCE, HConstants.NO_NONCE);
491      ((ClusterConnection) TEST_UTIL.getAdmin().getConnection()).getMaster().mergeTableRegions(null,
492        request);
493    } catch (org.apache.hbase.thirdparty.com.google.protobuf.ServiceException m) {
494      Throwable t = m.getCause();
495      do {
496        if (t instanceof MergeRegionException) {
497          gotException = true;
498          break;
499        }
500        t = t.getCause();
501      } while (t != null);
502    }
503    assertTrue(gotException);
504  }
505
506  @Test(expected = IllegalArgumentException.class)
507  public void testInvalidColumnDescriptor() throws IOException {
508    ColumnFamilyDescriptorBuilder.of("/cfamily/name");
509  }
510
511  /**
512   * Test DFS replication for column families, where one CF has default replication(3) and the other
513   * is set to 1.
514   */
515  @Test
516  public void testHFileReplication() throws Exception {
517    final TableName tableName = TableName.valueOf(this.name.getMethodName());
518    String fn1 = "rep1";
519    String fn = "defaultRep";
520    TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName)
521      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(fn))
522      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(fn1))
523        .setDFSReplication((short) 1).build())
524      .build();
525    Table table = TEST_UTIL.createTable(htd, null);
526    TEST_UTIL.waitTableAvailable(tableName);
527    Put p = new Put(Bytes.toBytes("defaultRep_rk"));
528    byte[] q1 = Bytes.toBytes("q1");
529    byte[] v1 = Bytes.toBytes("v1");
530    p.addColumn(Bytes.toBytes(fn), q1, v1);
531    List<Put> puts = new ArrayList<>(2);
532    puts.add(p);
533    p = new Put(Bytes.toBytes("rep1_rk"));
534    p.addColumn(Bytes.toBytes(fn1), q1, v1);
535    puts.add(p);
536    try {
537      table.put(puts);
538      ADMIN.flush(tableName);
539
540      List<HRegion> regions = TEST_UTIL.getMiniHBaseCluster().getRegions(tableName);
541      for (HRegion r : regions) {
542        HStore store = r.getStore(Bytes.toBytes(fn));
543        for (HStoreFile sf : store.getStorefiles()) {
544          assertTrue(sf.toString().contains(fn));
545          assertTrue("Column family " + fn + " should have 3 copies",
546            CommonFSUtils.getDefaultReplication(TEST_UTIL.getTestFileSystem(), sf.getPath())
547                == (sf.getFileInfo().getFileStatus().getReplication()));
548        }
549
550        store = r.getStore(Bytes.toBytes(fn1));
551        for (HStoreFile sf : store.getStorefiles()) {
552          assertTrue(sf.toString().contains(fn1));
553          assertTrue("Column family " + fn1 + " should have only 1 copy",
554            1 == sf.getFileInfo().getFileStatus().getReplication());
555        }
556      }
557    } finally {
558      if (ADMIN.isTableEnabled(tableName)) {
559        ADMIN.disableTable(tableName);
560        ADMIN.deleteTable(tableName);
561      }
562    }
563  }
564
565  @Test
566  public void testMergeRegions() throws Exception {
567    final TableName tableName = TableName.valueOf(name.getMethodName());
568    TableDescriptor td = TableDescriptorBuilder.newBuilder(tableName)
569      .setColumnFamily(ColumnFamilyDescriptorBuilder.of("d")).build();
570    byte[][] splitRows = new byte[2][];
571    splitRows[0] = new byte[] { (byte) '3' };
572    splitRows[1] = new byte[] { (byte) '6' };
573    try {
574      TEST_UTIL.createTable(td, splitRows);
575      TEST_UTIL.waitTableAvailable(tableName);
576
577      List<RegionInfo> tableRegions;
578      RegionInfo regionA;
579      RegionInfo regionB;
580      RegionInfo regionC;
581      RegionInfo mergedChildRegion = null;
582
583      // merge with full name
584      tableRegions = ADMIN.getRegions(tableName);
585      assertEquals(3, tableRegions.size());
586      regionA = tableRegions.get(0);
587      regionB = tableRegions.get(1);
588      regionC = tableRegions.get(2);
589      // TODO convert this to version that is synchronous (See HBASE-16668)
590      ADMIN.mergeRegionsAsync(regionA.getRegionName(), regionB.getRegionName(), false).get(60,
591        TimeUnit.SECONDS);
592
593      tableRegions = ADMIN.getRegions(tableName);
594
595      assertEquals(2, tableRegions.size());
596      for (RegionInfo ri : tableRegions) {
597        if (regionC.compareTo(ri) != 0) {
598          mergedChildRegion = ri;
599          break;
600        }
601      }
602
603      assertNotNull(mergedChildRegion);
604      // Need to wait GC for merged child region is done.
605      HMaster services = TEST_UTIL.getHBaseCluster().getMaster();
606      CatalogJanitor cj = services.getCatalogJanitor();
607      assertTrue(cj.scan() > 0);
608      // Wait until all procedures settled down
609      while (!services.getMasterProcedureExecutor().getActiveProcIds().isEmpty()) {
610        Thread.sleep(200);
611      }
612
613      // TODO convert this to version that is synchronous (See HBASE-16668)
614      ADMIN.mergeRegionsAsync(regionC.getEncodedNameAsBytes(),
615        mergedChildRegion.getEncodedNameAsBytes(), false).get(60, TimeUnit.SECONDS);
616
617      assertEquals(1, ADMIN.getRegions(tableName).size());
618    } finally {
619      ADMIN.disableTable(tableName);
620      ADMIN.deleteTable(tableName);
621    }
622  }
623
624  @Test
625  public void testMergeRegionsInvalidRegionCount()
626    throws IOException, InterruptedException, ExecutionException {
627    TableName tableName = TableName.valueOf(name.getMethodName());
628    TableDescriptor td = TableDescriptorBuilder.newBuilder(tableName)
629      .setColumnFamily(ColumnFamilyDescriptorBuilder.of("d")).build();
630    byte[][] splitRows = new byte[2][];
631    splitRows[0] = new byte[] { (byte) '3' };
632    splitRows[1] = new byte[] { (byte) '6' };
633    try {
634      TEST_UTIL.createTable(td, splitRows);
635      TEST_UTIL.waitTableAvailable(tableName);
636
637      List<RegionInfo> tableRegions = ADMIN.getRegions(tableName);
638      // 0
639      try {
640        ADMIN.mergeRegionsAsync(new byte[0][0], false).get();
641        fail();
642      } catch (IllegalArgumentException e) {
643        // expected
644      }
645      // 1
646      try {
647        ADMIN.mergeRegionsAsync(new byte[][] { tableRegions.get(0).getEncodedNameAsBytes() }, false)
648          .get();
649        fail();
650      } catch (IllegalArgumentException e) {
651        // expected
652      }
653    } finally {
654      ADMIN.disableTable(tableName);
655      ADMIN.deleteTable(tableName);
656    }
657  }
658
659  @Test
660  public void testSplitShouldNotHappenIfSplitIsDisabledForTable() throws Exception {
661    final TableName tableName = TableName.valueOf(name.getMethodName());
662    TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName)
663      .setRegionSplitPolicyClassName(DisabledRegionSplitPolicy.class.getName())
664      .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f")).build();
665    Table table = TEST_UTIL.createTable(htd, null);
666    for (int i = 0; i < 10; i++) {
667      Put p = new Put(Bytes.toBytes("row" + i));
668      byte[] q1 = Bytes.toBytes("q1");
669      byte[] v1 = Bytes.toBytes("v1");
670      p.addColumn(Bytes.toBytes("f"), q1, v1);
671      table.put(p);
672    }
673    ADMIN.flush(tableName);
674    try {
675      ADMIN.split(tableName, Bytes.toBytes("row5"));
676      Threads.sleep(10000);
677    } catch (Exception e) {
678      // Nothing to do.
679    }
680    // Split should not happen.
681    List<RegionInfo> allRegions =
682      MetaTableAccessor.getTableRegions(ADMIN.getConnection(), tableName, true);
683    assertEquals(1, allRegions.size());
684  }
685}