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