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