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