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.master.assignment;
019
020import static org.apache.hadoop.hbase.master.assignment.AssignmentTestingUtil.insertData;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.io.IOException;
027import java.util.List;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.Cell;
030import org.apache.hadoop.hbase.CellUtil;
031import org.apache.hadoop.hbase.DoNotRetryIOException;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.HBaseTestingUtil;
034import org.apache.hadoop.hbase.HConstants;
035import org.apache.hadoop.hbase.TableName;
036import org.apache.hadoop.hbase.Waiter;
037import org.apache.hadoop.hbase.client.CompactionState;
038import org.apache.hadoop.hbase.client.Delete;
039import org.apache.hadoop.hbase.client.Get;
040import org.apache.hadoop.hbase.client.RegionInfo;
041import org.apache.hadoop.hbase.client.RegionReplicaUtil;
042import org.apache.hadoop.hbase.client.Result;
043import org.apache.hadoop.hbase.client.SnapshotDescription;
044import org.apache.hadoop.hbase.client.SnapshotType;
045import org.apache.hadoop.hbase.client.Table;
046import org.apache.hadoop.hbase.client.TableDescriptor;
047import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
048import org.apache.hadoop.hbase.master.RegionState;
049import org.apache.hadoop.hbase.master.procedure.MasterProcedureConstants;
050import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
051import org.apache.hadoop.hbase.master.procedure.MasterProcedureTestingUtility;
052import org.apache.hadoop.hbase.master.procedure.ModifyTableProcedure;
053import org.apache.hadoop.hbase.master.procedure.TestSnapshotProcedure;
054import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
055import org.apache.hadoop.hbase.procedure2.ProcedureMetrics;
056import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
057import org.apache.hadoop.hbase.regionserver.HRegion;
058import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
059import org.apache.hadoop.hbase.testclassification.MasterTests;
060import org.apache.hadoop.hbase.testclassification.MediumTests;
061import org.apache.hadoop.hbase.util.Bytes;
062import org.junit.After;
063import org.junit.AfterClass;
064import org.junit.Before;
065import org.junit.BeforeClass;
066import org.junit.ClassRule;
067import org.junit.Rule;
068import org.junit.Test;
069import org.junit.experimental.categories.Category;
070import org.junit.rules.TestName;
071import org.slf4j.Logger;
072import org.slf4j.LoggerFactory;
073
074import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
075import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos;
076
077@Category({ MasterTests.class, MediumTests.class })
078public class TestSplitTableRegionProcedure {
079
080  @ClassRule
081  public static final HBaseClassTestRule CLASS_RULE =
082    HBaseClassTestRule.forClass(TestSplitTableRegionProcedure.class);
083
084  private static final Logger LOG = LoggerFactory.getLogger(TestSplitTableRegionProcedure.class);
085
086  protected static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
087
088  private static String columnFamilyName1 = "cf1";
089  private static String columnFamilyName2 = "cf2";
090
091  private static final int startRowNum = 11;
092  private static final int rowCount = 60;
093
094  private AssignmentManager am;
095
096  private ProcedureMetrics splitProcMetrics;
097  private ProcedureMetrics assignProcMetrics;
098  private ProcedureMetrics unassignProcMetrics;
099
100  private long splitSubmittedCount = 0;
101  private long splitFailedCount = 0;
102  private long assignSubmittedCount = 0;
103  private long assignFailedCount = 0;
104  private long unassignSubmittedCount = 0;
105  private long unassignFailedCount = 0;
106
107  @Rule
108  public TestName name = new TestName();
109
110  private static void setupConf(Configuration conf) {
111    conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
112    conf.setLong(HConstants.MAJOR_COMPACTION_PERIOD, 0);
113    conf.set("hbase.coprocessor.region.classes",
114      RegionServerHostingReplicaSlowOpenCoprocessor.class.getName());
115  }
116
117  @BeforeClass
118  public static void setupCluster() throws Exception {
119    setupConf(UTIL.getConfiguration());
120    UTIL.startMiniCluster(3);
121  }
122
123  @AfterClass
124  public static void cleanupTest() throws Exception {
125    try {
126      UTIL.shutdownMiniCluster();
127    } catch (Exception e) {
128      LOG.warn("failure shutting down cluster", e);
129    }
130  }
131
132  @Before
133  public void setup() throws Exception {
134    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
135
136    // Turn off balancer so it doesn't cut in and mess up our placements.
137    UTIL.getAdmin().balancerSwitch(false, true);
138    // Turn off the meta scanner so it don't remove parent on us.
139    UTIL.getHBaseCluster().getMaster().setCatalogJanitorEnabled(false);
140    am = UTIL.getHBaseCluster().getMaster().getAssignmentManager();
141    splitProcMetrics = am.getAssignmentManagerMetrics().getSplitProcMetrics();
142    assignProcMetrics = am.getAssignmentManagerMetrics().getAssignProcMetrics();
143    unassignProcMetrics = am.getAssignmentManagerMetrics().getUnassignProcMetrics();
144  }
145
146  @After
147  public void tearDown() throws Exception {
148    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
149    for (TableDescriptor htd : UTIL.getAdmin().listTableDescriptors()) {
150      UTIL.deleteTable(htd.getTableName());
151    }
152  }
153
154  @Test
155  public void testRollbackForSplitTableRegionWithReplica() throws Exception {
156    final TableName tableName = TableName.valueOf(name.getMethodName());
157    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
158
159    RegionServerHostingReplicaSlowOpenCoprocessor.slowDownReplicaOpen = true;
160    RegionInfo[] regions =
161      MasterProcedureTestingUtility.createTable(procExec, tableName, null, columnFamilyName1);
162
163    TableDescriptor td = TableDescriptorBuilder.newBuilder(UTIL.getAdmin().getDescriptor(tableName))
164      .setRegionReplication(2).build();
165    procExec.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), td));
166
167    // wait until the primary region is online.
168    HBaseTestingUtil.await(2000, () -> {
169      try {
170        AssignmentManager am = UTIL.getHBaseCluster().getMaster().getAssignmentManager();
171        if (am == null) return false;
172        if (am.getRegionStates().getRegionState(regions[0]).isOpened()) {
173          return true;
174        }
175        return false;
176      } catch (Exception e) {
177        throw new RuntimeException(e);
178      }
179    });
180
181    // Split region of the table, it will fail and rollback as replica parent region
182    // is still at OPENING state.
183    long procId = procExec.submitProcedure(new SplitTableRegionProcedure(procExec.getEnvironment(),
184      regions[0], HConstants.CATALOG_FAMILY));
185    // Wait for the completion.
186    ProcedureTestingUtility.waitProcedure(procExec, procId);
187
188    // Let replica parent region open.
189    RegionServerHostingReplicaSlowOpenCoprocessor.slowDownReplicaOpen = false;
190
191    // wait until the replica region is online.
192    HBaseTestingUtil.await(2000, () -> {
193      try {
194        AssignmentManager am = UTIL.getHBaseCluster().getMaster().getAssignmentManager();
195        if (am == null) return false;
196        RegionInfo replicaRegion = RegionReplicaUtil.getRegionInfoForReplica(regions[0], 1);
197        if (
198          am.getRegionStates().getRegionState(replicaRegion) != null
199            && am.getRegionStates().getRegionState(replicaRegion).isOpened()
200        ) {
201          return true;
202        }
203        return false;
204      } catch (Exception e) {
205        throw new RuntimeException(e);
206      }
207    });
208
209    ProcedureTestingUtility.assertProcFailed(procExec, procId);
210    // There should not be any active OpenRegionProcedure
211    procExec.getActiveProceduresNoCopy()
212      .forEach(p -> assertFalse(p instanceof OpenRegionProcedure));
213
214    // Check that procedure rollback reverted parent region state to OPEN
215    AssignmentManager am = UTIL.getHBaseCluster().getMaster().getAssignmentManager();
216    RegionStateNode regionStateNode = am.getRegionStates().getRegionStateNode(regions[0]);
217    assertEquals(RegionState.State.OPEN, regionStateNode.getState());
218  }
219
220  @Test
221  public void testSplitTableRegion() throws Exception {
222    final TableName tableName = TableName.valueOf(name.getMethodName());
223    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
224
225    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null,
226      columnFamilyName1, columnFamilyName2);
227    insertData(UTIL, tableName, rowCount, startRowNum, columnFamilyName1, columnFamilyName2);
228    int splitRowNum = startRowNum + rowCount / 2;
229    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
230
231    assertTrue("not able to find a splittable region", regions != null);
232    assertTrue("not able to find a splittable region", regions.length == 1);
233
234    // collect AM metrics before test
235    collectAssignmentManagerMetrics();
236
237    // Split region of the table
238    long procId = procExec.submitProcedure(
239      new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], splitKey));
240    // Wait the completion
241    ProcedureTestingUtility.waitProcedure(procExec, procId);
242    ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
243
244    verify(tableName, splitRowNum);
245
246    assertEquals(splitSubmittedCount + 1, splitProcMetrics.getSubmittedCounter().getCount());
247    assertEquals(splitFailedCount, splitProcMetrics.getFailedCounter().getCount());
248    assertEquals(assignSubmittedCount + 2, assignProcMetrics.getSubmittedCounter().getCount());
249    assertEquals(assignFailedCount, assignProcMetrics.getFailedCounter().getCount());
250    assertEquals(unassignSubmittedCount + 1, unassignProcMetrics.getSubmittedCounter().getCount());
251    assertEquals(unassignFailedCount, unassignProcMetrics.getFailedCounter().getCount());
252  }
253
254  @Test
255  public void testSplitTableRegionNoStoreFile() throws Exception {
256    final TableName tableName = TableName.valueOf(name.getMethodName());
257    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
258
259    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null,
260      columnFamilyName1, columnFamilyName2);
261    int splitRowNum = startRowNum + rowCount / 2;
262    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
263
264    assertTrue("not able to find a splittable region", regions != null);
265    assertTrue("not able to find a splittable region", regions.length == 1);
266
267    // collect AM metrics before test
268    collectAssignmentManagerMetrics();
269
270    // Split region of the table
271    long procId = procExec.submitProcedure(
272      new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], splitKey));
273    // Wait the completion
274    ProcedureTestingUtility.waitProcedure(procExec, procId);
275    ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
276
277    assertTrue(UTIL.getMiniHBaseCluster().getRegions(tableName).size() == 2);
278    assertTrue(UTIL.countRows(tableName) == 0);
279
280    assertEquals(splitSubmittedCount + 1, splitProcMetrics.getSubmittedCounter().getCount());
281    assertEquals(splitFailedCount, splitProcMetrics.getFailedCounter().getCount());
282  }
283
284  @Test
285  public void testSplitTableRegionUnevenDaughter() throws Exception {
286    final TableName tableName = TableName.valueOf(name.getMethodName());
287    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
288
289    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null,
290      columnFamilyName1, columnFamilyName2);
291    insertData(UTIL, tableName, rowCount, startRowNum, columnFamilyName1, columnFamilyName2);
292    // Split to two daughters with one of them only has 1 row
293    int splitRowNum = startRowNum + rowCount / 4;
294    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
295
296    assertTrue("not able to find a splittable region", regions != null);
297    assertTrue("not able to find a splittable region", regions.length == 1);
298
299    // collect AM metrics before test
300    collectAssignmentManagerMetrics();
301
302    // Split region of the table
303    long procId = procExec.submitProcedure(
304      new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], splitKey));
305    // Wait the completion
306    ProcedureTestingUtility.waitProcedure(procExec, procId);
307    ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
308
309    verify(tableName, splitRowNum);
310
311    assertEquals(splitSubmittedCount + 1, splitProcMetrics.getSubmittedCounter().getCount());
312    assertEquals(splitFailedCount, splitProcMetrics.getFailedCounter().getCount());
313  }
314
315  @Test
316  public void testSplitTableRegionEmptyDaughter() throws Exception {
317    final TableName tableName = TableName.valueOf(name.getMethodName());
318    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
319
320    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null,
321      columnFamilyName1, columnFamilyName2);
322    insertData(UTIL, tableName, rowCount, startRowNum, columnFamilyName1, columnFamilyName2);
323    // Split to two daughters with one of them only has 1 row
324    int splitRowNum = startRowNum + rowCount;
325    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
326
327    assertTrue("not able to find a splittable region", regions != null);
328    assertTrue("not able to find a splittable region", regions.length == 1);
329
330    // collect AM metrics before test
331    collectAssignmentManagerMetrics();
332
333    // Split region of the table
334    long procId = procExec.submitProcedure(
335      new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], splitKey));
336    // Wait the completion
337    ProcedureTestingUtility.waitProcedure(procExec, procId);
338    ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
339
340    // Make sure one daughter has 0 rows.
341    List<HRegion> daughters = UTIL.getMiniHBaseCluster().getRegions(tableName);
342    assertTrue(daughters.size() == 2);
343    assertTrue(UTIL.countRows(tableName) == rowCount);
344    assertTrue(UTIL.countRows(daughters.get(0)) == 0 || UTIL.countRows(daughters.get(1)) == 0);
345
346    assertEquals(splitSubmittedCount + 1, splitProcMetrics.getSubmittedCounter().getCount());
347    assertEquals(splitFailedCount, splitProcMetrics.getFailedCounter().getCount());
348  }
349
350  @Test
351  public void testSplitTableRegionDeletedRowsDaughter() throws Exception {
352    final TableName tableName = TableName.valueOf(name.getMethodName());
353    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
354
355    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null,
356      columnFamilyName1, columnFamilyName2);
357    insertData(UTIL, tableName, rowCount, startRowNum, columnFamilyName1, columnFamilyName2);
358    // Split to two daughters with one of them only has 1 row
359    int splitRowNum = rowCount;
360    deleteData(tableName, splitRowNum);
361    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
362
363    assertTrue("not able to find a splittable region", regions != null);
364    assertTrue("not able to find a splittable region", regions.length == 1);
365
366    // collect AM metrics before test
367    collectAssignmentManagerMetrics();
368
369    // Split region of the table
370    long procId = procExec.submitProcedure(
371      new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], splitKey));
372    // Wait the completion
373    ProcedureTestingUtility.waitProcedure(procExec, procId);
374    ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
375
376    UTIL.getAdmin().majorCompact(tableName);
377    // waiting for the major compaction to complete
378    UTIL.waitFor(6000, new Waiter.Predicate<IOException>() {
379      @Override
380      public boolean evaluate() throws IOException {
381        return UTIL.getAdmin().getCompactionState(tableName) == CompactionState.NONE;
382      }
383    });
384
385    // Make sure one daughter has 0 rows.
386    List<HRegion> daughters = UTIL.getMiniHBaseCluster().getRegions(tableName);
387    assertTrue(daughters.size() == 2);
388    final int currentRowCount = splitRowNum - startRowNum;
389    assertTrue(UTIL.countRows(tableName) == currentRowCount);
390    assertTrue(UTIL.countRows(daughters.get(0)) == 0 || UTIL.countRows(daughters.get(1)) == 0);
391
392    assertEquals(splitSubmittedCount + 1, splitProcMetrics.getSubmittedCounter().getCount());
393    assertEquals(splitFailedCount, splitProcMetrics.getFailedCounter().getCount());
394  }
395
396  @Test
397  public void testInvalidSplitKey() throws Exception {
398    final TableName tableName = TableName.valueOf(name.getMethodName());
399    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
400
401    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null,
402      columnFamilyName1, columnFamilyName2);
403    insertData(UTIL, tableName, rowCount, startRowNum, columnFamilyName1, columnFamilyName2);
404
405    assertTrue("not able to find a splittable region", regions != null);
406    assertTrue("not able to find a splittable region", regions.length == 1);
407
408    // collect AM metrics before test
409    collectAssignmentManagerMetrics();
410
411    // Split region of the table with null split key
412    try {
413      long procId1 = procExec.submitProcedure(
414        new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], null));
415      ProcedureTestingUtility.waitProcedure(procExec, procId1);
416      fail("unexpected procedure start with invalid split-key");
417    } catch (DoNotRetryIOException e) {
418      LOG.debug("Expected Split procedure construction failure: " + e.getMessage());
419    }
420
421    assertEquals(splitSubmittedCount, splitProcMetrics.getSubmittedCounter().getCount());
422    assertEquals(splitFailedCount, splitProcMetrics.getFailedCounter().getCount());
423  }
424
425  @Test
426  public void testRollbackAndDoubleExecution() throws Exception {
427    final TableName tableName = TableName.valueOf(name.getMethodName());
428    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
429
430    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null,
431      columnFamilyName1, columnFamilyName2);
432    insertData(UTIL, tableName, rowCount, startRowNum, columnFamilyName1, columnFamilyName2);
433    int splitRowNum = startRowNum + rowCount / 2;
434    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
435
436    assertTrue("not able to find a splittable region", regions != null);
437    assertTrue("not able to find a splittable region", regions.length == 1);
438    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
439    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
440
441    // collect AM metrics before test
442    collectAssignmentManagerMetrics();
443
444    // Split region of the table
445    long procId = procExec.submitProcedure(
446      new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], splitKey));
447
448    // Failing before SPLIT_TABLE_REGION_UPDATE_META we should trigger the
449    // rollback
450    // NOTE: the 7 (number of SPLIT_TABLE_REGION_UPDATE_META step) is
451    // hardcoded, so you have to look at this test at least once when you add a new step.
452    int lastStep = 7;
453    MasterProcedureTestingUtility.testRollbackAndDoubleExecution(procExec, procId, lastStep, true);
454    // check that we have only 1 region
455    assertEquals(1, UTIL.getAdmin().getRegions(tableName).size());
456    UTIL.waitUntilAllRegionsAssigned(tableName);
457    List<HRegion> newRegions = UTIL.getMiniHBaseCluster().getRegions(tableName);
458    assertEquals(1, newRegions.size());
459    verifyData(newRegions.get(0), startRowNum, rowCount, Bytes.toBytes(columnFamilyName1),
460      Bytes.toBytes(columnFamilyName2));
461
462    assertEquals(splitSubmittedCount + 1, splitProcMetrics.getSubmittedCounter().getCount());
463    assertEquals(splitFailedCount + 1, splitProcMetrics.getFailedCounter().getCount());
464  }
465
466  @Test
467  public void testRecoveryAndDoubleExecution() throws Exception {
468    final TableName tableName = TableName.valueOf(name.getMethodName());
469    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
470
471    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null,
472      columnFamilyName1, columnFamilyName2);
473    insertData(UTIL, tableName, rowCount, startRowNum, columnFamilyName1, columnFamilyName2);
474    int splitRowNum = startRowNum + rowCount / 2;
475    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
476
477    assertTrue("not able to find a splittable region", regions != null);
478    assertTrue("not able to find a splittable region", regions.length == 1);
479    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
480    ProcedureTestingUtility.setKillIfHasParent(procExec, false);
481    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
482
483    // collect AM metrics before test
484    collectAssignmentManagerMetrics();
485
486    // Split region of the table
487    long procId = procExec.submitProcedure(
488      new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], splitKey));
489
490    // Restart the executor and execute the step twice
491    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId);
492    ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
493
494    verify(tableName, splitRowNum);
495
496    assertEquals(splitSubmittedCount + 1, splitProcMetrics.getSubmittedCounter().getCount());
497    assertEquals(splitFailedCount, splitProcMetrics.getFailedCounter().getCount());
498  }
499
500  @Test
501  public void testSplitWithoutPONR() throws Exception {
502    final TableName tableName = TableName.valueOf(name.getMethodName());
503    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
504
505    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null,
506      columnFamilyName1, columnFamilyName2);
507    insertData(UTIL, tableName, rowCount, startRowNum, columnFamilyName1, columnFamilyName2);
508    int splitRowNum = startRowNum + rowCount / 2;
509    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
510
511    assertTrue("not able to find a splittable region", regions != null);
512    assertTrue("not able to find a splittable region", regions.length == 1);
513    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
514    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
515
516    // Split region of the table
517    long procId = procExec.submitProcedure(
518      new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], splitKey));
519
520    // Execute until step 7 of split procedure
521    // NOTE: the 7 (number after SPLIT_TABLE_REGION_UPDATE_META step)
522    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId, 7, false);
523
524    // Unset Toggle Kill and make ProcExec work correctly
525    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
526    MasterProcedureTestingUtility.restartMasterProcedureExecutor(procExec);
527    ProcedureTestingUtility.waitProcedure(procExec, procId);
528
529    // Even split failed after step 4, it should still works fine
530    verify(tableName, splitRowNum);
531  }
532
533  @Test
534  public void testSplitRegionWhileTakingSnapshot() throws Exception {
535    final TableName tableName = TableName.valueOf(name.getMethodName());
536    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
537
538    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(procExec, tableName, null,
539      columnFamilyName1, columnFamilyName2);
540    int splitRowNum = startRowNum + rowCount / 2;
541    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
542
543    assertTrue("not able to find a splittable region", regions != null);
544    assertTrue("not able to find a splittable region", regions.length == 1);
545    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
546
547    // task snapshot
548    SnapshotDescription snapshot =
549      new SnapshotDescription("SnapshotProcedureTest", tableName, SnapshotType.FLUSH);
550    SnapshotProtos.SnapshotDescription snapshotProto =
551      ProtobufUtil.createHBaseProtosSnapshotDesc(snapshot);
552    snapshotProto = SnapshotDescriptionUtils.validate(snapshotProto,
553      UTIL.getHBaseCluster().getMaster().getConfiguration());
554    long snapshotProcId = procExec.submitProcedure(
555      new TestSnapshotProcedure.DelaySnapshotProcedure(procExec.getEnvironment(), snapshotProto));
556    UTIL.getHBaseCluster().getMaster().getSnapshotManager().registerSnapshotProcedure(snapshotProto,
557      snapshotProcId);
558
559    // collect AM metrics before test
560    collectAssignmentManagerMetrics();
561
562    // Split region of the table
563    long procId = procExec.submitProcedure(
564      new SplitTableRegionProcedure(procExec.getEnvironment(), regions[0], splitKey));
565    // Wait the completion
566    ProcedureTestingUtility.waitProcedure(procExec, procId);
567    ProcedureTestingUtility.waitProcedure(procExec, snapshotProcId);
568
569    ProcedureTestingUtility.assertProcFailed(procExec, procId);
570    ProcedureTestingUtility.assertProcNotFailed(procExec, snapshotProcId);
571
572    assertTrue(UTIL.getMiniHBaseCluster().getRegions(tableName).size() == 1);
573    assertTrue(UTIL.countRows(tableName) == 0);
574
575    assertEquals(splitSubmittedCount + 1, splitProcMetrics.getSubmittedCounter().getCount());
576    assertEquals(splitFailedCount + 1, splitProcMetrics.getFailedCounter().getCount());
577  }
578
579  @Test
580  public void testSplitDetectsModifyTableProcedure() throws Exception {
581    final TableName tableName = TableName.valueOf(name.getMethodName());
582    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
583
584    RegionInfo[] regions =
585      MasterProcedureTestingUtility.createTable(procExec, tableName, null, columnFamilyName1);
586    RegionServerHostingReplicaSlowOpenCoprocessor.slowDownReplicaOpen = true;
587    TableDescriptor td = TableDescriptorBuilder.newBuilder(UTIL.getAdmin().getDescriptor(tableName))
588      .setRegionReplication(2).build();
589    long modifyProcId =
590      procExec.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), td));
591
592    // Split region of the table, the SplitTableRegionProcedure will fail because there is a
593    // ModifyTableProcedure in progress
594    SplitTableRegionProcedure splitProcedure = new SplitTableRegionProcedure(
595      procExec.getEnvironment(), regions[0], HConstants.CATALOG_FAMILY);
596    long splitProcId = procExec.submitProcedure(splitProcedure);
597    ProcedureTestingUtility.waitProcedure(procExec, splitProcId);
598    ProcedureTestingUtility.assertProcFailed(procExec, splitProcId);
599
600    RegionServerHostingReplicaSlowOpenCoprocessor.slowDownReplicaOpen = false;
601    ProcedureTestingUtility.waitProcedure(procExec, modifyProcId);
602    ProcedureTestingUtility.assertProcNotFailed(procExec, modifyProcId);
603  }
604
605  private void deleteData(final TableName tableName, final int startDeleteRowNum)
606    throws IOException, InterruptedException {
607    Table t = UTIL.getConnection().getTable(tableName);
608    final int numRows = rowCount + startRowNum - startDeleteRowNum;
609    Delete d;
610    for (int i = startDeleteRowNum; i <= numRows + startDeleteRowNum; i++) {
611      d = new Delete(Bytes.toBytes("" + i));
612      t.delete(d);
613      if (i % 5 == 0) {
614        UTIL.getAdmin().flush(tableName);
615      }
616    }
617  }
618
619  private void verify(final TableName tableName, final int splitRowNum) throws IOException {
620    List<HRegion> daughters = UTIL.getMiniHBaseCluster().getRegions(tableName);
621    assertTrue(daughters.size() == 2);
622    LOG.info("Row Count = " + UTIL.countRows(tableName));
623    assertTrue(UTIL.countRows(tableName) == rowCount);
624    int startRow;
625    int numRows;
626    for (int i = 0; i < daughters.size(); i++) {
627      if (
628        Bytes.compareTo(daughters.get(i).getRegionInfo().getStartKey(), HConstants.EMPTY_BYTE_ARRAY)
629            == 0
630      ) {
631        startRow = startRowNum; // first region
632        numRows = splitRowNum - startRowNum;
633      } else {
634        startRow = splitRowNum;
635        numRows = rowCount + startRowNum - splitRowNum;
636      }
637      verifyData(daughters.get(i), startRow, numRows, Bytes.toBytes(columnFamilyName1),
638        Bytes.toBytes(columnFamilyName2));
639    }
640  }
641
642  private void verifyData(final HRegion newReg, final int startRow, final int numRows,
643    final byte[]... families) throws IOException {
644    for (int i = startRow; i < startRow + numRows; i++) {
645      byte[] row = Bytes.toBytes("" + i);
646      Get get = new Get(row);
647      Result result = newReg.get(get);
648      Cell[] raw = result.rawCells();
649      assertEquals(families.length, result.size());
650      for (int j = 0; j < families.length; j++) {
651        assertTrue(CellUtil.matchingRows(raw[j], row));
652        assertTrue(CellUtil.matchingFamily(raw[j], families[j]));
653      }
654    }
655  }
656
657  private ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() {
658    return UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor();
659  }
660
661  private void collectAssignmentManagerMetrics() {
662    splitSubmittedCount = splitProcMetrics.getSubmittedCounter().getCount();
663    splitFailedCount = splitProcMetrics.getFailedCounter().getCount();
664    assignSubmittedCount = assignProcMetrics.getSubmittedCounter().getCount();
665    assignFailedCount = assignProcMetrics.getFailedCounter().getCount();
666    unassignSubmittedCount = unassignProcMetrics.getSubmittedCounter().getCount();
667    unassignFailedCount = unassignProcMetrics.getFailedCounter().getCount();
668  }
669}