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.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertNotSame;
024import static org.junit.Assert.assertNull;
025import static org.junit.Assert.assertTrue;
026import static org.junit.Assert.fail;
027
028import java.io.IOException;
029import java.util.Collection;
030import java.util.List;
031import java.util.Map;
032import java.util.Optional;
033import java.util.concurrent.CountDownLatch;
034import java.util.concurrent.TimeUnit;
035import java.util.concurrent.atomic.AtomicBoolean;
036
037import org.apache.hadoop.conf.Configuration;
038import org.apache.hadoop.fs.FileSystem;
039import org.apache.hadoop.fs.Path;
040import org.apache.hadoop.hbase.Coprocessor;
041import org.apache.hadoop.hbase.CoprocessorEnvironment;
042import org.apache.hadoop.hbase.HBaseClassTestRule;
043import org.apache.hadoop.hbase.HBaseTestingUtility;
044import org.apache.hadoop.hbase.HColumnDescriptor;
045import org.apache.hadoop.hbase.HConstants;
046import org.apache.hadoop.hbase.HTableDescriptor;
047import org.apache.hadoop.hbase.MasterNotRunningException;
048import org.apache.hadoop.hbase.MetaTableAccessor;
049import org.apache.hadoop.hbase.MiniHBaseCluster;
050import org.apache.hadoop.hbase.ServerName;
051import org.apache.hadoop.hbase.TableName;
052import org.apache.hadoop.hbase.UnknownRegionException;
053import org.apache.hadoop.hbase.ZooKeeperConnectionException;
054import org.apache.hadoop.hbase.client.Admin;
055import org.apache.hadoop.hbase.client.CompactionState;
056import org.apache.hadoop.hbase.client.Consistency;
057import org.apache.hadoop.hbase.client.Delete;
058import org.apache.hadoop.hbase.client.DoNotRetryRegionException;
059import org.apache.hadoop.hbase.client.Get;
060import org.apache.hadoop.hbase.client.Mutation;
061import org.apache.hadoop.hbase.client.Put;
062import org.apache.hadoop.hbase.client.RegionInfo;
063import org.apache.hadoop.hbase.client.Result;
064import org.apache.hadoop.hbase.client.ResultScanner;
065import org.apache.hadoop.hbase.client.Scan;
066import org.apache.hadoop.hbase.client.Table;
067import org.apache.hadoop.hbase.client.TestReplicasClient.SlowMeCopro;
068import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
069import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
070import org.apache.hadoop.hbase.coprocessor.MasterObserver;
071import org.apache.hadoop.hbase.coprocessor.ObserverContext;
072import org.apache.hadoop.hbase.exceptions.UnexpectedStateException;
073import org.apache.hadoop.hbase.master.HMaster;
074import org.apache.hadoop.hbase.master.LoadBalancer;
075import org.apache.hadoop.hbase.master.MasterRpcServices;
076import org.apache.hadoop.hbase.master.NoSuchProcedureException;
077import org.apache.hadoop.hbase.master.RegionState;
078import org.apache.hadoop.hbase.master.RegionState.State;
079import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
080import org.apache.hadoop.hbase.master.assignment.RegionStates;
081import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
082import org.apache.hadoop.hbase.regionserver.compactions.CompactionContext;
083import org.apache.hadoop.hbase.regionserver.throttle.NoLimitThroughputController;
084import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
085import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionStateTransition.TransitionCode;
086import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.ReportRegionStateTransitionRequest;
087import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.ReportRegionStateTransitionResponse;
088import org.apache.hadoop.hbase.testclassification.LargeTests;
089import org.apache.hadoop.hbase.testclassification.RegionServerTests;
090import org.apache.hadoop.hbase.util.Bytes;
091import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
092import org.apache.hadoop.hbase.util.FSUtils;
093import org.apache.hadoop.hbase.util.HBaseFsck;
094import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
095import org.apache.hadoop.hbase.util.RetryCounter;
096import org.apache.hadoop.hbase.util.Threads;
097import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
098import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
099import org.apache.zookeeper.KeeperException;
100import org.apache.zookeeper.KeeperException.NodeExistsException;
101import org.junit.After;
102import org.junit.AfterClass;
103import org.junit.Assert;
104import org.junit.Before;
105import org.junit.BeforeClass;
106import org.junit.ClassRule;
107import org.junit.Rule;
108import org.junit.Test;
109import org.junit.experimental.categories.Category;
110import org.junit.rules.TestName;
111import org.slf4j.Logger;
112import org.slf4j.LoggerFactory;
113
114/**
115 * The below tests are testing split region against a running cluster
116 */
117@Category({RegionServerTests.class, LargeTests.class})
118@SuppressWarnings("deprecation")
119public class TestSplitTransactionOnCluster {
120
121  @ClassRule
122  public static final HBaseClassTestRule CLASS_RULE =
123      HBaseClassTestRule.forClass(TestSplitTransactionOnCluster.class);
124
125  private static final Logger LOG = LoggerFactory.getLogger(TestSplitTransactionOnCluster.class);
126  private Admin admin = null;
127  private MiniHBaseCluster cluster = null;
128  private static final int NB_SERVERS = 3;
129
130  static final HBaseTestingUtility TESTING_UTIL =
131    new HBaseTestingUtility();
132
133  @Rule
134  public TestName name = new TestName();
135
136  @BeforeClass public static void before() throws Exception {
137    TESTING_UTIL.getConfiguration().setInt(HConstants.HBASE_BALANCER_PERIOD, 60000);
138    TESTING_UTIL.startMiniCluster(1, NB_SERVERS, null, MyMaster.class, null);
139  }
140
141  @AfterClass public static void after() throws Exception {
142    TESTING_UTIL.shutdownMiniCluster();
143  }
144
145  @Before public void setup() throws IOException {
146    TESTING_UTIL.ensureSomeNonStoppedRegionServersAvailable(NB_SERVERS);
147    this.admin = TESTING_UTIL.getAdmin();
148    this.cluster = TESTING_UTIL.getMiniHBaseCluster();
149  }
150
151  @After
152  public void tearDown() throws Exception {
153    this.admin.close();
154    for (HTableDescriptor htd: this.admin.listTables()) {
155      LOG.info("Tear down, remove table=" + htd.getTableName());
156      TESTING_UTIL.deleteTable(htd.getTableName());
157    }
158  }
159
160  private RegionInfo getAndCheckSingleTableRegion(final List<HRegion> regions)
161      throws IOException, InterruptedException {
162    assertEquals(1, regions.size());
163    RegionInfo hri = regions.get(0).getRegionInfo();
164    try {
165      cluster.getMaster().getAssignmentManager().waitForAssignment(hri, 600000);
166    } catch (NoSuchProcedureException e) {
167      LOG.info("Presume the procedure has been cleaned up so just proceed: " + e.toString());
168    }
169    return hri;
170  }
171
172  private void requestSplitRegion(
173      final HRegionServer rsServer,
174      final Region region,
175      final byte[] midKey) throws IOException {
176    long procId = cluster.getMaster().splitRegion(region.getRegionInfo(), midKey, 0, 0);
177    // wait for the split to complete or get interrupted.  If the split completes successfully,
178    // the procedure will return true; if the split fails, the procedure would throw exception.
179    ProcedureTestingUtility.waitProcedure(cluster.getMaster().getMasterProcedureExecutor(), procId);
180  }
181
182  @Test
183  public void testRITStateForRollback() throws Exception {
184    final TableName tableName = TableName.valueOf(name.getMethodName());
185    final HMaster master = cluster.getMaster();
186    try {
187      // Create table then get the single region for our new table.
188      Table t = createTableAndWait(tableName, Bytes.toBytes("cf"));
189      final List<HRegion> regions = cluster.getRegions(tableName);
190      final RegionInfo hri = getAndCheckSingleTableRegion(regions);
191      insertData(tableName, admin, t);
192      t.close();
193
194      // Turn off balancer so it doesn't cut in and mess up our placements.
195      this.admin.setBalancerRunning(false, true);
196      // Turn off the meta scanner so it don't remove parent on us.
197      master.setCatalogJanitorEnabled(false);
198
199      // find a splittable region
200      final HRegion region = findSplittableRegion(regions);
201      assertTrue("not able to find a splittable region", region != null);
202
203      // install master co-processor to fail splits
204      master.getMasterCoprocessorHost().load(
205        FailingSplitMasterObserver.class,
206        Coprocessor.PRIORITY_USER,
207        master.getConfiguration());
208
209      // split async
210      this.admin.splitRegion(region.getRegionInfo().getRegionName(), new byte[] {42});
211
212      // we have to wait until the SPLITTING state is seen by the master
213      FailingSplitMasterObserver observer =
214          master.getMasterCoprocessorHost().findCoprocessor(FailingSplitMasterObserver.class);
215      assertNotNull(observer);
216      observer.latch.await();
217
218      LOG.info("Waiting for region to come out of RIT");
219      while (!cluster.getMaster().getAssignmentManager().getRegionStates().isRegionOnline(hri)) {
220        Threads.sleep(100);
221      }
222      assertTrue(cluster.getMaster().getAssignmentManager().getRegionStates().isRegionOnline(hri));
223    } finally {
224      admin.setBalancerRunning(true, false);
225      master.setCatalogJanitorEnabled(true);
226      abortAndWaitForMaster();
227      TESTING_UTIL.deleteTable(tableName);
228    }
229  }
230
231  @Test
232  public void testSplitFailedCompactionAndSplit() throws Exception {
233    final TableName tableName = TableName.valueOf(name.getMethodName());
234    // Create table then get the single region for our new table.
235    HTableDescriptor htd = new HTableDescriptor(tableName);
236    byte[] cf = Bytes.toBytes("cf");
237    htd.addFamily(new HColumnDescriptor(cf));
238    admin.createTable(htd);
239
240    for (int i = 0; cluster.getRegions(tableName).isEmpty() && i < 100; i++) {
241      Thread.sleep(100);
242    }
243    assertEquals(1, cluster.getRegions(tableName).size());
244
245    HRegion region = cluster.getRegions(tableName).get(0);
246    HStore store = region.getStore(cf);
247    int regionServerIndex = cluster.getServerWith(region.getRegionInfo().getRegionName());
248    HRegionServer regionServer = cluster.getRegionServer(regionServerIndex);
249
250    Table t = TESTING_UTIL.getConnection().getTable(tableName);
251    // insert data
252    insertData(tableName, admin, t);
253    insertData(tableName, admin, t);
254
255    int fileNum = store.getStorefiles().size();
256    // 0, Compaction Request
257    store.triggerMajorCompaction();
258    Optional<CompactionContext> cc = store.requestCompaction();
259    assertTrue(cc.isPresent());
260    // 1, A timeout split
261    // 1.1 close region
262    assertEquals(2, region.close(false).get(cf).size());
263    // 1.2 rollback and Region initialize again
264    region.initialize();
265
266    // 2, Run Compaction cc
267    assertFalse(region.compact(cc.get(), store, NoLimitThroughputController.INSTANCE));
268    assertTrue(fileNum > store.getStorefiles().size());
269
270    // 3, Split
271    requestSplitRegion(regionServer, region, Bytes.toBytes("row3"));
272    assertEquals(2, cluster.getRegions(tableName).size());
273  }
274
275  public static class FailingSplitMasterObserver implements MasterCoprocessor, MasterObserver {
276    volatile CountDownLatch latch;
277
278    @Override
279    public void start(CoprocessorEnvironment e) throws IOException {
280      latch = new CountDownLatch(1);
281    }
282
283    @Override
284    public Optional<MasterObserver> getMasterObserver() {
285      return Optional.of(this);
286    }
287
288    @Override
289    public void preSplitRegionBeforeMETAAction(
290        final ObserverContext<MasterCoprocessorEnvironment> ctx,
291        final byte[] splitKey,
292        final List<Mutation> metaEntries) throws IOException {
293      latch.countDown();
294      throw new IOException("Causing rollback of region split");
295    }
296  }
297
298  @Test
299  public void testSplitRollbackOnRegionClosing() throws IOException, InterruptedException {
300    final TableName tableName = TableName.valueOf(name.getMethodName());
301
302    // Create table then get the single region for our new table.
303    Table t = createTableAndWait(tableName, HConstants.CATALOG_FAMILY);
304    List<HRegion> regions = cluster.getRegions(tableName);
305    RegionInfo hri = getAndCheckSingleTableRegion(regions);
306
307    int tableRegionIndex = ensureTableRegionNotOnSameServerAsMeta(admin, hri);
308
309    RegionStates regionStates = cluster.getMaster().getAssignmentManager().getRegionStates();
310
311    // Turn off balancer so it doesn't cut in and mess up our placements.
312    this.admin.setBalancerRunning(false, true);
313    // Turn off the meta scanner so it don't remove parent on us.
314    cluster.getMaster().setCatalogJanitorEnabled(false);
315    try {
316      // Add a bit of load up into the table so splittable.
317      TESTING_UTIL.loadTable(t, HConstants.CATALOG_FAMILY, false);
318      // Get region pre-split.
319      HRegionServer server = cluster.getRegionServer(tableRegionIndex);
320      printOutRegions(server, "Initial regions: ");
321      int regionCount = cluster.getRegions(hri.getTable()).size();
322      regionStates.updateRegionState(hri, RegionState.State.CLOSING);
323
324      // Now try splitting.... should fail.  And each should successfully
325      // rollback.
326      // We don't roll back here anymore. Instead we fail-fast on construction of the
327      // split transaction. Catch the exception instead.
328      try {
329        this.admin.splitRegion(hri.getRegionName());
330        fail();
331      } catch (DoNotRetryRegionException e) {
332        // Expected
333      }
334      // Wait around a while and assert count of regions remains constant.
335      for (int i = 0; i < 10; i++) {
336        Thread.sleep(100);
337        assertEquals(regionCount, cluster.getRegions(hri.getTable()).size());
338      }
339      regionStates.updateRegionState(hri, State.OPEN);
340      // Now try splitting and it should work.
341      split(hri, server, regionCount);
342      // Get daughters
343      checkAndGetDaughters(tableName);
344      // OK, so split happened after we cleared the blocking node.
345    } finally {
346      admin.setBalancerRunning(true, false);
347      cluster.getMaster().setCatalogJanitorEnabled(true);
348      t.close();
349    }
350  }
351
352  /**
353   * Test that if daughter split on us, we won't do the shutdown handler fixup
354   * just because we can't find the immediate daughter of an offlined parent.
355   * @throws IOException
356   * @throws InterruptedException
357   */
358  @Test
359  public void testShutdownFixupWhenDaughterHasSplit()throws IOException, InterruptedException {
360    final TableName tableName = TableName.valueOf(name.getMethodName());
361
362    // Create table then get the single region for our new table.
363    Table t = createTableAndWait(tableName, HConstants.CATALOG_FAMILY);
364    List<HRegion> regions = cluster.getRegions(tableName);
365    RegionInfo hri = getAndCheckSingleTableRegion(regions);
366
367    int tableRegionIndex = ensureTableRegionNotOnSameServerAsMeta(admin, hri);
368
369    // Turn off balancer so it doesn't cut in and mess up our placements.
370    this.admin.setBalancerRunning(false, true);
371    // Turn off the meta scanner so it don't remove parent on us.
372    cluster.getMaster().setCatalogJanitorEnabled(false);
373    try {
374      // Add a bit of load up into the table so splittable.
375      TESTING_UTIL.loadTable(t, HConstants.CATALOG_FAMILY);
376      // Get region pre-split.
377      HRegionServer server = cluster.getRegionServer(tableRegionIndex);
378      printOutRegions(server, "Initial regions: ");
379      int regionCount = cluster.getRegions(hri.getTable()).size();
380      // Now split.
381      split(hri, server, regionCount);
382      // Get daughters
383      List<HRegion> daughters = checkAndGetDaughters(tableName);
384      // Now split one of the daughters.
385      regionCount = cluster.getRegions(hri.getTable()).size();
386      RegionInfo daughter = daughters.get(0).getRegionInfo();
387      LOG.info("Daughter we are going to split: " + daughter);
388      // Compact first to ensure we have cleaned up references -- else the split
389      // will fail.
390      this.admin.compactRegion(daughter.getRegionName());
391      RetryCounter retrier = new RetryCounter(30, 1, TimeUnit.SECONDS);
392      while (CompactionState.NONE != admin.getCompactionStateForRegion(daughter.getRegionName())
393          && retrier.shouldRetry()) {
394        retrier.sleepUntilNextRetry();
395      }
396      daughters = cluster.getRegions(tableName);
397      HRegion daughterRegion = null;
398      for (HRegion r : daughters) {
399        if (RegionInfo.COMPARATOR.compare(r.getRegionInfo(), daughter) == 0) {
400          daughterRegion = r;
401          // Archiving the compacted references file
402          r.getStores().get(0).closeAndArchiveCompactedFiles();
403          LOG.info("Found matching HRI: " + daughterRegion);
404          break;
405        }
406      }
407      assertTrue(daughterRegion != null);
408      for (int i=0; i<100; i++) {
409        if (!daughterRegion.hasReferences()) break;
410        Threads.sleep(100);
411      }
412      assertFalse("Waiting for reference to be compacted", daughterRegion.hasReferences());
413      LOG.info("Daughter hri before split (has been compacted): " + daughter);
414      split(daughter, server, regionCount);
415      // Get list of daughters
416      daughters = cluster.getRegions(tableName);
417      for (HRegion d: daughters) {
418        LOG.info("Regions before crash: " + d);
419      }
420      // Now crash the server
421      cluster.abortRegionServer(tableRegionIndex);
422      waitUntilRegionServerDead();
423      awaitDaughters(tableName, daughters.size());
424      // Assert daughters are online and ONLY the original daughters -- that
425      // fixup didn't insert one during server shutdown recover.
426      regions = cluster.getRegions(tableName);
427      for (HRegion d: daughters) {
428        LOG.info("Regions after crash: " + d);
429      }
430      if (daughters.size() != regions.size()) {
431        LOG.info("Daughters=" + daughters.size() + ", regions=" + regions.size());
432      }
433      assertEquals(daughters.size(), regions.size());
434      for (HRegion r: regions) {
435        LOG.info("Regions post crash " + r + ", contains=" + daughters.contains(r));
436        assertTrue("Missing region post crash " + r, daughters.contains(r));
437      }
438    } finally {
439      LOG.info("EXITING");
440      admin.setBalancerRunning(true, false);
441      cluster.getMaster().setCatalogJanitorEnabled(true);
442      t.close();
443    }
444  }
445
446  @Test
447  public void testSplitShouldNotThrowNPEEvenARegionHasEmptySplitFiles() throws Exception {
448    TableName userTableName = TableName.valueOf(name.getMethodName());
449    HTableDescriptor htd = new HTableDescriptor(userTableName);
450    HColumnDescriptor hcd = new HColumnDescriptor("col");
451    htd.addFamily(hcd);
452    admin.createTable(htd);
453    Table table = TESTING_UTIL.getConnection().getTable(userTableName);
454    try {
455      for (int i = 0; i <= 5; i++) {
456        String row = "row" + i;
457        Put p = new Put(row.getBytes());
458        String val = "Val" + i;
459        p.addColumn("col".getBytes(), "ql".getBytes(), val.getBytes());
460        table.put(p);
461        admin.flush(userTableName);
462        Delete d = new Delete(row.getBytes());
463        // Do a normal delete
464        table.delete(d);
465        admin.flush(userTableName);
466      }
467      admin.majorCompact(userTableName);
468      List<RegionInfo> regionsOfTable =
469          cluster.getMaster().getAssignmentManager().getRegionStates()
470          .getRegionsOfTable(userTableName);
471      assertEquals(1, regionsOfTable.size());
472      RegionInfo hRegionInfo = regionsOfTable.get(0);
473      Put p = new Put("row6".getBytes());
474      p.addColumn("col".getBytes(), "ql".getBytes(), "val".getBytes());
475      table.put(p);
476      p = new Put("row7".getBytes());
477      p.addColumn("col".getBytes(), "ql".getBytes(), "val".getBytes());
478      table.put(p);
479      p = new Put("row8".getBytes());
480      p.addColumn("col".getBytes(), "ql".getBytes(), "val".getBytes());
481      table.put(p);
482      admin.flush(userTableName);
483      admin.splitRegion(hRegionInfo.getRegionName(), "row7".getBytes());
484      regionsOfTable = cluster.getMaster()
485          .getAssignmentManager().getRegionStates()
486          .getRegionsOfTable(userTableName);
487
488      while (regionsOfTable.size() != 2) {
489        Thread.sleep(1000);
490        regionsOfTable = cluster.getMaster()
491            .getAssignmentManager().getRegionStates()
492            .getRegionsOfTable(userTableName);
493        LOG.debug("waiting 2 regions to be available, got " + regionsOfTable.size() +
494          ": " + regionsOfTable);
495
496      }
497      Assert.assertEquals(2, regionsOfTable.size());
498
499      Scan s = new Scan();
500      ResultScanner scanner = table.getScanner(s);
501      int mainTableCount = 0;
502      for (Result rr = scanner.next(); rr != null; rr = scanner.next()) {
503        mainTableCount++;
504      }
505      Assert.assertEquals(3, mainTableCount);
506    } finally {
507      table.close();
508    }
509  }
510
511  /**
512   * Verifies HBASE-5806.  Here the case is that splitting is completed but before the
513   * CJ could remove the parent region the master is killed and restarted.
514   * @throws IOException
515   * @throws InterruptedException
516   * @throws NodeExistsException
517   * @throws KeeperException
518   */
519  @Test
520  public void testMasterRestartAtRegionSplitPendingCatalogJanitor()
521      throws IOException, InterruptedException, NodeExistsException,
522      KeeperException, ServiceException {
523    final TableName tableName = TableName.valueOf(name.getMethodName());
524
525    // Create table then get the single region for our new table.
526    Table t = createTableAndWait(tableName, HConstants.CATALOG_FAMILY);
527    List<HRegion> regions = cluster.getRegions(tableName);
528    RegionInfo hri = getAndCheckSingleTableRegion(regions);
529
530    int tableRegionIndex = ensureTableRegionNotOnSameServerAsMeta(admin, hri);
531
532    // Turn off balancer so it doesn't cut in and mess up our placements.
533    this.admin.setBalancerRunning(false, true);
534    // Turn off the meta scanner so it don't remove parent on us.
535    cluster.getMaster().setCatalogJanitorEnabled(false);
536    try {
537      // Add a bit of load up into the table so splittable.
538      TESTING_UTIL.loadTable(t, HConstants.CATALOG_FAMILY, false);
539      // Get region pre-split.
540      HRegionServer server = cluster.getRegionServer(tableRegionIndex);
541      printOutRegions(server, "Initial regions: ");
542      // Call split.
543      this.admin.splitRegion(hri.getRegionName());
544      List<HRegion> daughters = checkAndGetDaughters(tableName);
545
546      // Before cleanup, get a new master.
547      HMaster master = abortAndWaitForMaster();
548      // Now call compact on the daughters and clean up any references.
549      for (HRegion daughter : daughters) {
550        daughter.compact(true);
551        RetryCounter retrier = new RetryCounter(30, 1, TimeUnit.SECONDS);
552        while (CompactionState.NONE != admin
553            .getCompactionStateForRegion(daughter.getRegionInfo().getRegionName())
554            && retrier.shouldRetry()) {
555          retrier.sleepUntilNextRetry();
556        }
557        daughter.getStores().get(0).closeAndArchiveCompactedFiles();
558        assertFalse(daughter.hasReferences());
559      }
560      // BUT calling compact on the daughters is not enough. The CatalogJanitor looks
561      // in the filesystem, and the filesystem content is not same as what the Region
562      // is reading from. Compacted-away files are picked up later by the compacted
563      // file discharger process. It runs infrequently. Make it run so CatalogJanitor
564      // doens't find any references.
565      for (RegionServerThread rst: cluster.getRegionServerThreads()) {
566        boolean oldSetting = rst.getRegionServer().compactedFileDischarger.setUseExecutor(false);
567        rst.getRegionServer().compactedFileDischarger.run();
568        rst.getRegionServer().compactedFileDischarger.setUseExecutor(oldSetting);
569      }
570      cluster.getMaster().setCatalogJanitorEnabled(true);
571      LOG.info("Starting run of CatalogJanitor");
572      cluster.getMaster().getCatalogJanitor().run();
573      ProcedureTestingUtility.waitAllProcedures(cluster.getMaster().getMasterProcedureExecutor());
574      RegionStates regionStates = master.getAssignmentManager().getRegionStates();
575      ServerName regionServerOfRegion = regionStates.getRegionServerOfRegion(hri);
576      assertEquals(null, regionServerOfRegion);
577    } finally {
578      TESTING_UTIL.getAdmin().setBalancerRunning(true, false);
579      cluster.getMaster().setCatalogJanitorEnabled(true);
580      t.close();
581    }
582  }
583
584  @Test
585  public void testSplitWithRegionReplicas() throws Exception {
586    final TableName tableName = TableName.valueOf(name.getMethodName());
587    HTableDescriptor htd = TESTING_UTIL.createTableDescriptor(name.getMethodName());
588    htd.setRegionReplication(2);
589    htd.addCoprocessor(SlowMeCopro.class.getName());
590    // Create table then get the single region for our new table.
591    Table t = TESTING_UTIL.createTable(htd, new byte[][]{Bytes.toBytes("cf")}, null);
592    List<HRegion> oldRegions;
593    do {
594      oldRegions = cluster.getRegions(tableName);
595      Thread.sleep(10);
596    } while (oldRegions.size() != 2);
597    for (HRegion h : oldRegions) LOG.debug("OLDREGION " + h.getRegionInfo());
598    try {
599      int regionServerIndex = cluster.getServerWith(oldRegions.get(0).getRegionInfo()
600        .getRegionName());
601      HRegionServer regionServer = cluster.getRegionServer(regionServerIndex);
602      insertData(tableName, admin, t);
603      // Turn off balancer so it doesn't cut in and mess up our placements.
604      admin.setBalancerRunning(false, true);
605      // Turn off the meta scanner so it don't remove parent on us.
606      cluster.getMaster().setCatalogJanitorEnabled(false);
607      boolean tableExists = MetaTableAccessor.tableExists(regionServer.getConnection(),
608          tableName);
609      assertEquals("The specified table should be present.", true, tableExists);
610      final HRegion region = findSplittableRegion(oldRegions);
611      regionServerIndex = cluster.getServerWith(region.getRegionInfo().getRegionName());
612      regionServer = cluster.getRegionServer(regionServerIndex);
613      assertTrue("not able to find a splittable region", region != null);
614      try {
615        requestSplitRegion(regionServer, region, Bytes.toBytes("row2"));
616      } catch (IOException e) {
617        e.printStackTrace();
618        fail("Split execution should have succeeded with no exceptions thrown " + e);
619      }
620      //TESTING_UTIL.waitUntilAllRegionsAssigned(tableName);
621      List<HRegion> newRegions;
622      do {
623        newRegions = cluster.getRegions(tableName);
624        for (HRegion h : newRegions) LOG.debug("NEWREGION " + h.getRegionInfo());
625        Thread.sleep(1000);
626      } while ((newRegions.contains(oldRegions.get(0)) || newRegions.contains(oldRegions.get(1)))
627          || newRegions.size() != 4);
628      tableExists = MetaTableAccessor.tableExists(regionServer.getConnection(),
629          tableName);
630      assertEquals("The specified table should be present.", true, tableExists);
631      // exists works on stale and we see the put after the flush
632      byte[] b1 = "row1".getBytes();
633      Get g = new Get(b1);
634      g.setConsistency(Consistency.STRONG);
635      // The following GET will make a trip to the meta to get the new location of the 1st daughter
636      // In the process it will also get the location of the replica of the daughter (initially
637      // pointing to the parent's replica)
638      Result r = t.get(g);
639      Assert.assertFalse(r.isStale());
640      LOG.info("exists stale after flush done");
641
642      SlowMeCopro.getPrimaryCdl().set(new CountDownLatch(1));
643      g = new Get(b1);
644      g.setConsistency(Consistency.TIMELINE);
645      // This will succeed because in the previous GET we get the location of the replica
646      r = t.get(g);
647      Assert.assertTrue(r.isStale());
648      SlowMeCopro.getPrimaryCdl().get().countDown();
649    } finally {
650      SlowMeCopro.getPrimaryCdl().get().countDown();
651      admin.setBalancerRunning(true, false);
652      cluster.getMaster().setCatalogJanitorEnabled(true);
653      t.close();
654    }
655  }
656
657  private void insertData(final TableName tableName, Admin admin, Table t) throws IOException,
658      InterruptedException {
659    Put p = new Put(Bytes.toBytes("row1"));
660    p.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("q1"), Bytes.toBytes("1"));
661    t.put(p);
662    p = new Put(Bytes.toBytes("row2"));
663    p.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("q1"), Bytes.toBytes("2"));
664    t.put(p);
665    p = new Put(Bytes.toBytes("row3"));
666    p.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("q1"), Bytes.toBytes("3"));
667    t.put(p);
668    p = new Put(Bytes.toBytes("row4"));
669    p.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("q1"), Bytes.toBytes("4"));
670    t.put(p);
671    admin.flush(tableName);
672  }
673
674  /**
675   * If a table has regions that have no store files in a region, they should split successfully
676   * into two regions with no store files.
677   */
678  @Test
679  public void testSplitRegionWithNoStoreFiles()
680      throws Exception {
681    final TableName tableName = TableName.valueOf(name.getMethodName());
682    // Create table then get the single region for our new table.
683    createTableAndWait(tableName, HConstants.CATALOG_FAMILY);
684    List<HRegion> regions = cluster.getRegions(tableName);
685    RegionInfo hri = getAndCheckSingleTableRegion(regions);
686    ensureTableRegionNotOnSameServerAsMeta(admin, hri);
687    int regionServerIndex = cluster.getServerWith(regions.get(0).getRegionInfo()
688      .getRegionName());
689    HRegionServer regionServer = cluster.getRegionServer(regionServerIndex);
690    // Turn off balancer so it doesn't cut in and mess up our placements.
691    this.admin.setBalancerRunning(false, true);
692    // Turn off the meta scanner so it don't remove parent on us.
693    cluster.getMaster().setCatalogJanitorEnabled(false);
694    try {
695      // Precondition: we created a table with no data, no store files.
696      printOutRegions(regionServer, "Initial regions: ");
697      Configuration conf = cluster.getConfiguration();
698      HBaseFsck.debugLsr(conf, new Path("/"));
699      Path rootDir = FSUtils.getRootDir(conf);
700      FileSystem fs = TESTING_UTIL.getDFSCluster().getFileSystem();
701      Map<String, Path> storefiles =
702          FSUtils.getTableStoreFilePathMap(null, fs, rootDir, tableName);
703      assertEquals("Expected nothing but found " + storefiles.toString(), 0, storefiles.size());
704
705      // find a splittable region.  Refresh the regions list
706      regions = cluster.getRegions(tableName);
707      final HRegion region = findSplittableRegion(regions);
708      assertTrue("not able to find a splittable region", region != null);
709
710      // Now split.
711      try {
712        requestSplitRegion(regionServer, region, Bytes.toBytes("row2"));
713      } catch (IOException e) {
714        fail("Split execution should have succeeded with no exceptions thrown");
715      }
716
717      // Postcondition: split the table with no store files into two regions, but still have no
718      // store files
719      List<HRegion> daughters = cluster.getRegions(tableName);
720      assertEquals(2, daughters.size());
721
722      // check dirs
723      HBaseFsck.debugLsr(conf, new Path("/"));
724      Map<String, Path> storefilesAfter =
725          FSUtils.getTableStoreFilePathMap(null, fs, rootDir, tableName);
726      assertEquals("Expected nothing but found " + storefilesAfter.toString(), 0,
727          storefilesAfter.size());
728
729      hri = region.getRegionInfo(); // split parent
730      AssignmentManager am = cluster.getMaster().getAssignmentManager();
731      RegionStates regionStates = am.getRegionStates();
732      long start = EnvironmentEdgeManager.currentTime();
733      while (!regionStates.isRegionInState(hri, State.SPLIT)) {
734        LOG.debug("Waiting for SPLIT state on: " + hri);
735        assertFalse("Timed out in waiting split parent to be in state SPLIT",
736          EnvironmentEdgeManager.currentTime() - start > 60000);
737        Thread.sleep(500);
738      }
739      assertTrue(regionStates.isRegionInState(daughters.get(0).getRegionInfo(), State.OPEN));
740      assertTrue(regionStates.isRegionInState(daughters.get(1).getRegionInfo(), State.OPEN));
741
742      // We should not be able to assign it again
743      am.assign(hri);
744      assertFalse("Split region can't be assigned",
745        regionStates.isRegionInTransition(hri));
746      assertTrue(regionStates.isRegionInState(hri, State.SPLIT));
747
748      // We should not be able to unassign it either
749      try {
750        am.unassign(hri);
751        fail("Should have thrown exception");
752      } catch (UnexpectedStateException e) {
753        // Expected
754      }
755      assertFalse("Split region can't be unassigned",
756        regionStates.isRegionInTransition(hri));
757      assertTrue(regionStates.isRegionInState(hri, State.SPLIT));
758    } finally {
759      admin.setBalancerRunning(true, false);
760      cluster.getMaster().setCatalogJanitorEnabled(true);
761    }
762  }
763
764  @Test
765  public void testStoreFileReferenceCreationWhenSplitPolicySaysToSkipRangeCheck()
766      throws Exception {
767    final TableName tableName = TableName.valueOf(name.getMethodName());
768    try {
769      HTableDescriptor htd = new HTableDescriptor(tableName);
770      htd.addFamily(new HColumnDescriptor("f"));
771      htd.addFamily(new HColumnDescriptor("i_f"));
772      htd.setRegionSplitPolicyClassName(CustomSplitPolicy.class.getName());
773      admin.createTable(htd);
774      List<HRegion> regions = awaitTableRegions(tableName);
775      HRegion region = regions.get(0);
776      for(int i = 3;i<9;i++) {
777        Put p = new Put(Bytes.toBytes("row"+i));
778        p.addColumn(Bytes.toBytes("f"), Bytes.toBytes("q"), Bytes.toBytes("value" + i));
779        p.addColumn(Bytes.toBytes("i_f"), Bytes.toBytes("q"), Bytes.toBytes("value" + i));
780        region.put(p);
781      }
782      region.flush(true);
783      HStore store = region.getStore(Bytes.toBytes("f"));
784      Collection<HStoreFile> storefiles = store.getStorefiles();
785      assertEquals(1, storefiles.size());
786      assertFalse(region.hasReferences());
787      Path referencePath =
788          region.getRegionFileSystem().splitStoreFile(region.getRegionInfo(), "f",
789            storefiles.iterator().next(), Bytes.toBytes("row1"), false, region.getSplitPolicy());
790      assertNull(referencePath);
791      referencePath =
792          region.getRegionFileSystem().splitStoreFile(region.getRegionInfo(), "i_f",
793            storefiles.iterator().next(), Bytes.toBytes("row1"), false, region.getSplitPolicy());
794      assertNotNull(referencePath);
795    } finally {
796      TESTING_UTIL.deleteTable(tableName);
797    }
798  }
799
800  private HRegion findSplittableRegion(final List<HRegion> regions) throws InterruptedException {
801    for (int i = 0; i < 5; ++i) {
802      for (HRegion r: regions) {
803        if (r.isSplittable() && r.getRegionInfo().getReplicaId() == 0) {
804          return(r);
805        }
806      }
807      Thread.sleep(100);
808    }
809    return(null);
810  }
811
812  private List<HRegion> checkAndGetDaughters(TableName tableName)
813      throws InterruptedException {
814    List<HRegion> daughters = null;
815    // try up to 10s
816    for (int i=0; i<100; i++) {
817      daughters = cluster.getRegions(tableName);
818      if (daughters.size() >= 2) break;
819      Thread.sleep(100);
820    }
821    assertTrue(daughters.size() >= 2);
822    return daughters;
823  }
824
825  private HMaster abortAndWaitForMaster()
826  throws IOException, InterruptedException {
827    cluster.abortMaster(0);
828    cluster.waitOnMaster(0);
829    HMaster master = cluster.startMaster().getMaster();
830    cluster.waitForActiveAndReadyMaster();
831    return master;
832  }
833
834  private void split(final RegionInfo hri, final HRegionServer server, final int regionCount)
835      throws IOException, InterruptedException {
836    admin.splitRegion(hri.getRegionName());
837    for (int i = 0; cluster.getRegions(hri.getTable()).size() <= regionCount && i < 60; i++) {
838      LOG.debug("Waiting on region " + hri.getRegionNameAsString() + " to split");
839      Thread.sleep(2000);
840    }
841    assertFalse("Waited too long for split",
842        cluster.getRegions(hri.getTable()).size() <= regionCount);
843  }
844
845  /**
846   * Ensure single table region is not on same server as the single hbase:meta table
847   * region.
848   * @param admin
849   * @param hri
850   * @return Index of the server hosting the single table region
851   * @throws UnknownRegionException
852   * @throws MasterNotRunningException
853   * @throws org.apache.hadoop.hbase.ZooKeeperConnectionException
854   * @throws InterruptedException
855   */
856  private int ensureTableRegionNotOnSameServerAsMeta(final Admin admin,
857      final RegionInfo hri)
858  throws IOException, MasterNotRunningException,
859  ZooKeeperConnectionException, InterruptedException {
860    // Now make sure that the table region is not on same server as that hosting
861    // hbase:meta  We don't want hbase:meta replay polluting our test when we later crash
862    // the table region serving server.
863    int metaServerIndex = cluster.getServerWithMeta();
864    boolean tablesOnMaster = LoadBalancer.isTablesOnMaster(TESTING_UTIL.getConfiguration());
865    if (tablesOnMaster) {
866      // Need to check master is supposed to host meta... perhaps it is not.
867      throw new UnsupportedOperationException();
868      // TODO: assertTrue(metaServerIndex == -1); // meta is on master now
869    }
870    HRegionServer metaRegionServer = tablesOnMaster?
871      cluster.getMaster(): cluster.getRegionServer(metaServerIndex);
872    int tableRegionIndex = cluster.getServerWith(hri.getRegionName());
873    assertTrue(tableRegionIndex != -1);
874    HRegionServer tableRegionServer = cluster.getRegionServer(tableRegionIndex);
875    LOG.info("MetaRegionServer=" + metaRegionServer.getServerName() +
876      ", other=" + tableRegionServer.getServerName());
877    if (metaRegionServer.getServerName().equals(tableRegionServer.getServerName())) {
878      HRegionServer hrs = getOtherRegionServer(cluster, metaRegionServer);
879      assertNotNull(hrs);
880      assertNotNull(hri);
881      LOG.info("Moving " + hri.getRegionNameAsString() + " from " +
882        metaRegionServer.getServerName() + " to " +
883        hrs.getServerName() + "; metaServerIndex=" + metaServerIndex);
884      admin.move(hri.getEncodedNameAsBytes(), Bytes.toBytes(hrs.getServerName().toString()));
885    }
886    // Wait till table region is up on the server that is NOT carrying hbase:meta.
887    for (int i = 0; i < 100; i++) {
888      tableRegionIndex = cluster.getServerWith(hri.getRegionName());
889      if (tableRegionIndex != -1 && tableRegionIndex != metaServerIndex) break;
890      LOG.debug("Waiting on region move off the hbase:meta server; current index " +
891        tableRegionIndex + " and metaServerIndex=" + metaServerIndex);
892      Thread.sleep(100);
893    }
894    assertTrue("Region not moved off hbase:meta server, tableRegionIndex=" + tableRegionIndex,
895      tableRegionIndex != -1 && tableRegionIndex != metaServerIndex);
896    // Verify for sure table region is not on same server as hbase:meta
897    tableRegionIndex = cluster.getServerWith(hri.getRegionName());
898    assertTrue(tableRegionIndex != -1);
899    assertNotSame(metaServerIndex, tableRegionIndex);
900    return tableRegionIndex;
901  }
902
903  /**
904   * Find regionserver other than the one passed.
905   * Can't rely on indexes into list of regionservers since crashed servers
906   * occupy an index.
907   * @param cluster
908   * @param notThisOne
909   * @return A regionserver that is not <code>notThisOne</code> or null if none
910   * found
911   */
912  private HRegionServer getOtherRegionServer(final MiniHBaseCluster cluster,
913      final HRegionServer notThisOne) {
914    for (RegionServerThread rst: cluster.getRegionServerThreads()) {
915      HRegionServer hrs = rst.getRegionServer();
916      if (hrs.getServerName().equals(notThisOne.getServerName())) continue;
917      if (hrs.isStopping() || hrs.isStopped()) continue;
918      return hrs;
919    }
920    return null;
921  }
922
923  private void printOutRegions(final HRegionServer hrs, final String prefix)
924      throws IOException {
925    List<RegionInfo> regions = ProtobufUtil.getOnlineRegions(hrs.getRSRpcServices());
926    for (RegionInfo region: regions) {
927      LOG.info(prefix + region.getRegionNameAsString());
928    }
929  }
930
931  private void waitUntilRegionServerDead() throws InterruptedException, IOException {
932    // Wait until the master processes the RS shutdown
933    for (int i=0; (cluster.getMaster().getClusterMetrics()
934        .getLiveServerMetrics().size() > NB_SERVERS
935        || cluster.getLiveRegionServerThreads().size() > NB_SERVERS) && i<100; i++) {
936      LOG.info("Waiting on server to go down");
937      Thread.sleep(100);
938    }
939    assertFalse("Waited too long for RS to die",
940      cluster.getMaster().getClusterMetrics(). getLiveServerMetrics().size() > NB_SERVERS
941        || cluster.getLiveRegionServerThreads().size() > NB_SERVERS);
942  }
943
944  private void awaitDaughters(TableName tableName, int numDaughters) throws InterruptedException {
945    // Wait till regions are back on line again.
946    for (int i = 0; cluster.getRegions(tableName).size() < numDaughters && i < 60; i++) {
947      LOG.info("Waiting for repair to happen");
948      Thread.sleep(1000);
949    }
950    if (cluster.getRegions(tableName).size() < numDaughters) {
951      fail("Waiting too long for daughter regions");
952    }
953  }
954
955  private List<HRegion> awaitTableRegions(final TableName tableName) throws InterruptedException {
956    List<HRegion> regions = null;
957    for (int i = 0; i < 100; i++) {
958      regions = cluster.getRegions(tableName);
959      if (regions.size() > 0) break;
960      Thread.sleep(100);
961    }
962    return regions;
963  }
964
965  private Table createTableAndWait(TableName tableName, byte[] cf) throws IOException,
966      InterruptedException {
967    Table t = TESTING_UTIL.createTable(tableName, cf);
968    awaitTableRegions(tableName);
969    assertTrue("Table not online: " + tableName,
970      cluster.getRegions(tableName).size() != 0);
971    return t;
972  }
973
974  // Make it public so that JVMClusterUtil can access it.
975  public static class MyMaster extends HMaster {
976    public MyMaster(Configuration conf) throws IOException, KeeperException, InterruptedException {
977      super(conf);
978    }
979
980    @Override
981    protected RSRpcServices createRpcServices() throws IOException {
982      return new MyMasterRpcServices(this);
983    }
984  }
985
986  static class MyMasterRpcServices extends MasterRpcServices {
987    static AtomicBoolean enabled = new AtomicBoolean(false);
988
989    private HMaster myMaster;
990    public MyMasterRpcServices(HMaster master) throws IOException {
991      super(master);
992      myMaster = master;
993    }
994
995    @Override
996    public ReportRegionStateTransitionResponse reportRegionStateTransition(RpcController c,
997        ReportRegionStateTransitionRequest req) throws ServiceException {
998      ReportRegionStateTransitionResponse resp = super.reportRegionStateTransition(c, req);
999      if (enabled.get() && req.getTransition(0).getTransitionCode().equals(
1000          TransitionCode.READY_TO_SPLIT) && !resp.hasErrorMessage()) {
1001        RegionStates regionStates = myMaster.getAssignmentManager().getRegionStates();
1002        for (RegionStates.RegionStateNode regionState:
1003          regionStates.getRegionsInTransition()) {
1004          /* TODO!!!!
1005          // Find the merging_new region and remove it
1006          if (regionState.isSplittingNew()) {
1007            regionStates.deleteRegion(regionState.getRegion());
1008          }
1009          */
1010        }
1011      }
1012      return resp;
1013    }
1014  }
1015
1016  static class CustomSplitPolicy extends IncreasingToUpperBoundRegionSplitPolicy {
1017
1018    @Override
1019    protected boolean shouldSplit() {
1020      return true;
1021    }
1022
1023    @Override
1024    public boolean skipStoreFileRangeCheck(String familyName) {
1025      if(familyName.startsWith("i_")) {
1026        return true;
1027      } else {
1028        return false;
1029      }
1030    }
1031  }
1032}
1033