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.HBaseClassTestRule;
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.apache.hadoop.hbase.util.Threads;
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    Threads.sleep(10000);
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    }
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(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    }
229  }
230
231  /**
232   * Method to enable merge for the passed table and validate this modification.
233   * @param tableName name of the table
234   */
235  private void enableTableMerge(final TableName tableName) throws Exception {
236    // Get the original table descriptor
237    TableDescriptor originalTableDesc = admin.getDescriptor(tableName);
238    TableDescriptor modifiedTableDesc =
239      TableDescriptorBuilder.newBuilder(originalTableDesc).setMergeEnabled(true).build();
240
241    // Now modify the table descriptor and enable merge for it
242    admin.modifyTable(modifiedTableDesc);
243
244    // Verify that merge is enabled
245    assertTrue(admin.getDescriptor(tableName).isMergeEnabled());
246  }
247
248  private void tryMergeAndEnsureItIsSuccess(final TableName tableName) throws Exception {
249    // assert we have at least 2 regions in the table
250    List<RegionInfo> regions = admin.getRegions(tableName);
251    int originalCount = regions.size();
252    assertTrue(originalCount >= 2);
253
254    byte[] nameOfRegionA = regions.get(0).getEncodedNameAsBytes();
255    byte[] nameOfRegionB = regions.get(1).getEncodedNameAsBytes();
256
257    // merge the table regions and wait until region count decreases
258    admin.mergeRegionsAsync(nameOfRegionA, nameOfRegionB, true);
259    TEST_UTIL.waitFor(30000, new ExplainingPredicate<Exception>() {
260
261      @Override
262      public boolean evaluate() throws Exception {
263        return admin.getRegions(tableName).size() < originalCount;
264      }
265
266      @Override
267      public String explainFailure() throws Exception {
268        return "Merge has not finished yet";
269      }
270    });
271  }
272}