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.assertFalse;
021import static org.junit.jupiter.api.Assertions.assertTrue;
022import static org.junit.jupiter.api.Assertions.fail;
023
024import java.util.List;
025import java.util.concurrent.ExecutionException;
026import java.util.concurrent.Future;
027import java.util.concurrent.TimeUnit;
028import org.apache.hadoop.hbase.DoNotRetryIOException;
029import org.apache.hadoop.hbase.HBaseTestingUtil;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.Waiter.ExplainingPredicate;
032import org.apache.hadoop.hbase.testclassification.ClientTests;
033import org.apache.hadoop.hbase.testclassification.MediumTests;
034import org.apache.hadoop.hbase.util.Bytes;
035import org.junit.jupiter.api.AfterAll;
036import org.junit.jupiter.api.BeforeAll;
037import org.junit.jupiter.api.BeforeEach;
038import org.junit.jupiter.api.Tag;
039import org.junit.jupiter.api.Test;
040import org.junit.jupiter.api.TestInfo;
041
042@Tag(MediumTests.TAG)
043@Tag(ClientTests.TAG)
044public class TestSplitOrMergeAtTableLevel {
045
046  private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
047  private static byte[] FAMILY = Bytes.toBytes("testFamily");
048
049  private String methodName;
050  private static Admin admin;
051
052  @BeforeEach
053  public void setUp(TestInfo testInfo) {
054    this.methodName = testInfo.getTestMethod().get().getName();
055  }
056
057  @BeforeAll
058  public static void setUpBeforeClass() throws Exception {
059    TEST_UTIL.startMiniCluster(2);
060    admin = TEST_UTIL.getAdmin();
061  }
062
063  @AfterAll
064  public static void tearDownAfterClass() throws Exception {
065    TEST_UTIL.shutdownMiniCluster();
066  }
067
068  @Test
069  public void testTableSplitSwitch() throws Exception {
070    final TableName tableName = TableName.valueOf(methodName);
071    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tableName)
072      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).setSplitEnabled(false).build();
073
074    // create a table with split disabled
075    Table t = TEST_UTIL.createTable(tableDesc, null);
076    TEST_UTIL.waitTableAvailable(tableName);
077
078    // load data into the table
079    TEST_UTIL.loadTable(t, FAMILY, false);
080
081    assertTrue(admin.getRegions(tableName).size() == 1);
082
083    // check that we have split disabled
084    assertFalse(admin.getDescriptor(tableName).isSplitEnabled());
085    trySplitAndEnsureItFails(tableName);
086    enableTableSplit(tableName);
087    trySplitAndEnsureItIsSuccess(tableName);
088  }
089
090  @Test
091  public void testTableSplitSwitchForPreSplittedTable() throws Exception {
092    final TableName tableName = TableName.valueOf(methodName);
093
094    // create a table with split disabled
095    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tableName)
096      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).setSplitEnabled(false).build();
097    Table t = TEST_UTIL.createTable(tableDesc, new byte[][] { Bytes.toBytes(10) });
098    TEST_UTIL.waitTableAvailable(tableName);
099
100    // load data into the table
101    TEST_UTIL.loadTable(t, FAMILY, false);
102
103    assertTrue(admin.getRegions(tableName).size() == 2);
104
105    // check that we have split disabled
106    assertFalse(admin.getDescriptor(tableName).isSplitEnabled());
107    trySplitAndEnsureItFails(tableName);
108    enableTableSplit(tableName);
109    trySplitAndEnsureItIsSuccess(tableName);
110  }
111
112  @Test
113  public void testTableMergeSwitch() throws Exception {
114    final TableName tableName = TableName.valueOf(methodName);
115
116    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tableName)
117      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).setMergeEnabled(false).build();
118
119    Table t = TEST_UTIL.createTable(tableDesc, null);
120    TEST_UTIL.waitTableAvailable(tableName);
121    TEST_UTIL.loadTable(t, FAMILY, false);
122
123    // check merge is disabled for the table
124    assertFalse(admin.getDescriptor(tableName).isMergeEnabled());
125
126    trySplitAndEnsureItIsSuccess(tableName);
127
128    tryMergeAndEnsureItFails(tableName);
129    admin.disableTable(tableName);
130    enableTableMerge(tableName);
131    admin.enableTable(tableName);
132    tryMergeAndEnsureItIsSuccess(tableName);
133  }
134
135  @Test
136  public void testTableMergeSwitchForPreSplittedTable() throws Exception {
137    final TableName tableName = TableName.valueOf(methodName);
138
139    TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tableName)
140      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).setMergeEnabled(false).build();
141
142    Table t = TEST_UTIL.createTable(tableDesc, new byte[][] { Bytes.toBytes(10) });
143    TEST_UTIL.waitTableAvailable(tableName);
144    TEST_UTIL.loadTable(t, FAMILY, false);
145
146    // check merge is disabled for the table
147    assertFalse(admin.getDescriptor(tableName).isMergeEnabled());
148    assertTrue(admin.getRegions(tableName).size() == 2);
149    tryMergeAndEnsureItFails(tableName);
150    enableTableMerge(tableName);
151    tryMergeAndEnsureItIsSuccess(tableName);
152  }
153
154  private void trySplitAndEnsureItFails(final TableName tableName) throws Exception {
155    // get the original table region count
156    List<RegionInfo> regions = admin.getRegions(tableName);
157    int originalCount = regions.size();
158
159    // split the table and make sure region count does not increase
160    Future<?> f = admin.splitRegionAsync(regions.get(0).getEncodedNameAsBytes(), Bytes.toBytes(2));
161    try {
162      f.get(10, TimeUnit.SECONDS);
163      fail("Should not get here.");
164    } catch (ExecutionException ee) {
165      // expected to reach here
166      // check and ensure that table does not get splitted
167      assertTrue(admin.getRegions(tableName).size() == originalCount);
168      assertTrue(ee.getCause() instanceof DoNotRetryIOException, "Expected DoNotRetryIOException!");
169    }
170  }
171
172  /**
173   * Method to enable split for the passed table and validate this modification.
174   * @param tableName name of the table
175   */
176  private void enableTableSplit(final TableName tableName) throws Exception {
177    // Get the original table descriptor
178    TableDescriptor originalTableDesc = admin.getDescriptor(tableName);
179    TableDescriptor modifiedTableDesc =
180      TableDescriptorBuilder.newBuilder(originalTableDesc).setSplitEnabled(true).build();
181
182    // Now modify the table descriptor and enable split for it
183    admin.modifyTable(modifiedTableDesc);
184
185    // Verify that split is enabled
186    assertTrue(admin.getDescriptor(tableName).isSplitEnabled());
187  }
188
189  private void trySplitAndEnsureItIsSuccess(final TableName tableName) throws Exception {
190    // get the original table region count
191    List<RegionInfo> regions = admin.getRegions(tableName);
192    int originalCount = regions.size();
193
194    // split the table and wait until region count increases
195    admin.split(tableName, Bytes.toBytes(3));
196    TEST_UTIL.waitFor(30000, new ExplainingPredicate<Exception>() {
197
198      @Override
199      public boolean evaluate() throws Exception {
200        return admin.getRegions(tableName).size() > originalCount;
201      }
202
203      @Override
204      public String explainFailure() throws Exception {
205        return "Split has not finished yet";
206      }
207    });
208  }
209
210  private void tryMergeAndEnsureItFails(final TableName tableName) throws Exception {
211    // assert we have at least 2 regions in the table
212    List<RegionInfo> regions = admin.getRegions(tableName);
213    int originalCount = regions.size();
214    assertTrue(originalCount >= 2);
215
216    byte[] nameOfRegionA = regions.get(0).getEncodedNameAsBytes();
217    byte[] nameOfRegionB = regions.get(1).getEncodedNameAsBytes();
218
219    // check and ensure that region do not get merged
220    Future<?> f = admin.mergeRegionsAsync(new byte[][] { nameOfRegionA, nameOfRegionB }, true);
221    try {
222      f.get(10, TimeUnit.SECONDS);
223      fail("Should not get here.");
224    } catch (ExecutionException ee) {
225      // expected to reach here
226      // check and ensure that region do not get merged
227      assertTrue(admin.getRegions(tableName).size() == originalCount);
228      assertTrue(ee.getCause() instanceof DoNotRetryIOException, "Expected DoNotRetryIOException!");
229    }
230  }
231
232  /**
233   * Method to enable merge for the passed table and validate this modification.
234   * @param tableName name of the table
235   */
236  private void enableTableMerge(final TableName tableName) throws Exception {
237    // Get the original table descriptor
238    TableDescriptor originalTableDesc = admin.getDescriptor(tableName);
239    TableDescriptor modifiedTableDesc =
240      TableDescriptorBuilder.newBuilder(originalTableDesc).setMergeEnabled(true).build();
241
242    // Now modify the table descriptor and enable merge for it
243    admin.modifyTable(modifiedTableDesc);
244
245    // Verify that merge is enabled
246    assertTrue(admin.getDescriptor(tableName).isMergeEnabled());
247  }
248
249  private void tryMergeAndEnsureItIsSuccess(final TableName tableName) throws Exception {
250    // assert we have at least 2 regions in the table
251    List<RegionInfo> regions = admin.getRegions(tableName);
252    int originalCount = regions.size();
253    assertTrue(originalCount >= 2);
254
255    byte[] nameOfRegionA = regions.get(0).getEncodedNameAsBytes();
256    byte[] nameOfRegionB = regions.get(1).getEncodedNameAsBytes();
257
258    // merge the table regions and wait until region count decreases
259    admin.mergeRegionsAsync(new byte[][] { nameOfRegionA, nameOfRegionB }, true);
260    TEST_UTIL.waitFor(30000, new ExplainingPredicate<Exception>() {
261
262      @Override
263      public boolean evaluate() throws Exception {
264        return admin.getRegions(tableName).size() < originalCount;
265      }
266
267      @Override
268      public String explainFailure() throws Exception {
269        return "Merge has not finished yet";
270      }
271    });
272  }
273}