001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.client;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023import static org.junit.jupiter.api.Assertions.fail;
024
025import java.io.IOException;
026import java.util.List;
027import java.util.concurrent.ExecutionException;
028import java.util.concurrent.Future;
029import java.util.concurrent.TimeUnit;
030import org.apache.hadoop.hbase.DoNotRetryIOException;
031import org.apache.hadoop.hbase.HBaseTestingUtil;
032import org.apache.hadoop.hbase.ServerName;
033import org.apache.hadoop.hbase.TableName;
034import org.apache.hadoop.hbase.master.assignment.AssignmentTestingUtil;
035import org.apache.hadoop.hbase.master.assignment.SplitTableRegionProcedure;
036import org.apache.hadoop.hbase.master.procedure.DeleteTableProcedure;
037import org.apache.hadoop.hbase.master.procedure.DisableTableProcedure;
038import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
039import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
040import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
041import org.apache.hadoop.hbase.testclassification.ClientTests;
042import org.apache.hadoop.hbase.testclassification.MediumTests;
043import org.apache.hadoop.hbase.util.Bytes;
044import org.apache.hadoop.hbase.util.Threads;
045import org.junit.jupiter.api.AfterEach;
046import org.junit.jupiter.api.BeforeEach;
047import org.junit.jupiter.api.Disabled;
048import org.junit.jupiter.api.Tag;
049import org.junit.jupiter.api.Test;
050import org.junit.jupiter.api.TestInfo;
051
052@Tag(MediumTests.TAG)
053@Tag(ClientTests.TAG)
054public class TestSplitOrMergeStatus {
055
056  private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
057  private static byte[] FAMILY = Bytes.toBytes("testFamily");
058
059  private String methodName;
060
061  @BeforeEach
062  public void setUp(TestInfo info) throws Exception {
063    methodName = info.getTestMethod().get().getName();
064    TEST_UTIL.startMiniCluster(2);
065  }
066
067  @AfterEach
068  public void tearDown() throws Exception {
069    TEST_UTIL.shutdownMiniCluster();
070  }
071
072  @Test
073  public void testSplitSwitch() throws Exception {
074    final TableName tableName = TableName.valueOf(methodName);
075    Table t = TEST_UTIL.createTable(tableName, FAMILY);
076    TEST_UTIL.loadTable(t, FAMILY, false);
077
078    RegionLocator locator = TEST_UTIL.getConnection().getRegionLocator(t.getName());
079    int originalCount = locator.getAllRegionLocations().size();
080
081    Admin admin = TEST_UTIL.getAdmin();
082    initSwitchStatus(admin);
083    assertTrue(admin.splitSwitch(false, false));
084    try {
085      admin.split(t.getName());
086      fail("Shouldn't get here");
087    } catch (DoNotRetryIOException dnioe) {
088      // Expected
089    }
090    int count = admin.getRegions(tableName).size();
091    assertTrue(originalCount == count);
092    assertFalse(admin.splitSwitch(true, false));
093    admin.split(t.getName());
094    while ((count = admin.getRegions(tableName).size()) == originalCount) {
095      Threads.sleep(1);
096    }
097    count = admin.getRegions(tableName).size();
098    assertTrue(originalCount < count);
099    admin.close();
100  }
101
102  @Disabled
103  @Test
104  public void testMergeSwitch() throws Exception {
105    final TableName tableName = TableName.valueOf(methodName);
106    Table t = TEST_UTIL.createTable(tableName, FAMILY);
107    TEST_UTIL.loadTable(t, FAMILY, false);
108
109    Admin admin = TEST_UTIL.getAdmin();
110    int originalCount = admin.getRegions(tableName).size();
111    initSwitchStatus(admin);
112    admin.split(t.getName());
113    int postSplitCount = -1;
114    while ((postSplitCount = admin.getRegions(tableName).size()) == originalCount) {
115      Threads.sleep(1);
116    }
117    assertTrue(originalCount != postSplitCount,
118      "originalCount=" + originalCount + ", newCount=" + postSplitCount);
119
120    // Merge switch is off so merge should NOT succeed.
121    boolean result = admin.mergeSwitch(false, false);
122    assertTrue(result);
123    List<RegionInfo> regions = admin.getRegions(t.getName());
124    assertTrue(regions.size() > 1);
125    Future<?> f = admin.mergeRegionsAsync(regions.get(0).getEncodedNameAsBytes(),
126      regions.get(1).getEncodedNameAsBytes(), true);
127    try {
128      f.get(10, TimeUnit.SECONDS);
129      fail("Should not get here.");
130    } catch (ExecutionException ee) {
131      // Expected.
132    }
133    int count = admin.getRegions(tableName).size();
134    assertTrue(postSplitCount == count, "newCount=" + postSplitCount + ", count=" + count);
135
136    result = admin.mergeSwitch(true, false);
137    regions = admin.getRegions(t.getName());
138    assertFalse(result);
139    f = admin.mergeRegionsAsync(regions.get(0).getEncodedNameAsBytes(),
140      regions.get(1).getEncodedNameAsBytes(), true);
141    f.get(10, TimeUnit.SECONDS);
142    count = admin.getRegions(tableName).size();
143    assertTrue((postSplitCount / 2 /* Merge */) == count);
144    admin.close();
145  }
146
147  @Test
148  public void testMultiSwitches() throws IOException {
149    Admin admin = TEST_UTIL.getAdmin();
150    assertTrue(admin.splitSwitch(false, false));
151    assertTrue(admin.mergeSwitch(false, false));
152
153    assertFalse(admin.isSplitEnabled());
154    assertFalse(admin.isMergeEnabled());
155    admin.close();
156  }
157
158  @Test
159  public void testSplitRegionReplicaRitRecovery() throws Exception {
160    int startRowNum = 11;
161    int rowCount = 60;
162    final TableName tableName = TableName.valueOf(methodName);
163    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
164    TEST_UTIL.getAdmin().createTable(TableDescriptorBuilder.newBuilder(tableName)
165      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).setRegionReplication(2).build());
166    TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
167    ServerName serverName =
168      RegionReplicaTestHelper.getRSCarryingReplica(TEST_UTIL, tableName, 1).get();
169    List<RegionInfo> regions = TEST_UTIL.getAdmin().getRegions(tableName);
170    insertData(tableName, startRowNum, rowCount);
171    int splitRowNum = startRowNum + rowCount / 2;
172    byte[] splitKey = Bytes.toBytes("" + splitRowNum);
173    // Split region of the table
174    long procId = procExec.submitProcedure(
175      new SplitTableRegionProcedure(procExec.getEnvironment(), regions.get(0), splitKey));
176    // Wait the completion
177    ProcedureTestingUtility.waitProcedure(procExec, procId);
178    // Disable the table
179    long procId1 = procExec
180      .submitProcedure(new DisableTableProcedure(procExec.getEnvironment(), tableName, false));
181    // Wait the completion
182    ProcedureTestingUtility.waitProcedure(procExec, procId1);
183    // Delete Table
184    long procId2 =
185      procExec.submitProcedure(new DeleteTableProcedure(procExec.getEnvironment(), tableName));
186    // Wait the completion
187    ProcedureTestingUtility.waitProcedure(procExec, procId2);
188    AssignmentTestingUtil.killRs(TEST_UTIL, serverName);
189    Threads.sleepWithoutInterrupt(5000);
190    boolean hasRegionsInTransition =
191      TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager().hasRegionsInTransition();
192    assertEquals(false, hasRegionsInTransition);
193  }
194
195  private ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() {
196    return TEST_UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor();
197  }
198
199  private void insertData(final TableName tableName, int startRow, int rowCount)
200    throws IOException {
201    Table t = TEST_UTIL.getConnection().getTable(tableName);
202    Put p;
203    for (int i = 0; i < rowCount; i++) {
204      p = new Put(Bytes.toBytes("" + (startRow + i)));
205      p.addColumn(FAMILY, Bytes.toBytes("q1"), Bytes.toBytes(i));
206      t.put(p);
207    }
208  }
209
210  private void initSwitchStatus(Admin admin) throws IOException {
211    if (!admin.isSplitEnabled()) {
212      admin.splitSwitch(true, false);
213    }
214    if (!admin.isMergeEnabled()) {
215      admin.mergeSwitch(true, false);
216    }
217    assertTrue(admin.isSplitEnabled());
218    assertTrue(admin.isMergeEnabled());
219  }
220}