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.apache.hadoop.hbase.TableName.META_TABLE_NAME; 021import static org.hamcrest.CoreMatchers.instanceOf; 022import static org.hamcrest.MatcherAssert.assertThat; 023import static org.junit.Assert.assertEquals; 024import static org.junit.Assert.assertFalse; 025import static org.junit.Assert.assertNotEquals; 026import static org.junit.Assert.assertNotNull; 027import static org.junit.Assert.assertTrue; 028import static org.junit.Assert.fail; 029 030import java.io.IOException; 031import java.util.ArrayList; 032import java.util.Collections; 033import java.util.List; 034import java.util.concurrent.ExecutionException; 035import java.util.stream.Collectors; 036import org.apache.hadoop.hbase.ClientMetaTableAccessor; 037import org.apache.hadoop.hbase.HBaseClassTestRule; 038import org.apache.hadoop.hbase.HConstants; 039import org.apache.hadoop.hbase.HRegionLocation; 040import org.apache.hadoop.hbase.TableName; 041import org.apache.hadoop.hbase.master.HMaster; 042import org.apache.hadoop.hbase.master.assignment.AssignmentTestingUtil; 043import org.apache.hadoop.hbase.master.janitor.CatalogJanitor; 044import org.apache.hadoop.hbase.testclassification.ClientTests; 045import org.apache.hadoop.hbase.testclassification.LargeTests; 046import org.apache.hadoop.hbase.util.Bytes; 047import org.apache.hadoop.hbase.util.Threads; 048import org.junit.ClassRule; 049import org.junit.Ignore; 050import org.junit.Test; 051import org.junit.experimental.categories.Category; 052import org.junit.runner.RunWith; 053import org.junit.runners.Parameterized; 054 055/** 056 * Class to test asynchronous region admin operations. 057 * @see TestAsyncRegionAdminApi This test and it used to be joined it was taking longer than our ten 058 * minute timeout so they were split. 059 */ 060@RunWith(Parameterized.class) 061@Category({ LargeTests.class, ClientTests.class }) 062public class TestAsyncRegionAdminApi2 extends TestAsyncAdminBase { 063 064 @ClassRule 065 public static final HBaseClassTestRule CLASS_RULE = 066 HBaseClassTestRule.forClass(TestAsyncRegionAdminApi2.class); 067 068 @Test 069 public void testGetRegionLocation() throws Exception { 070 RawAsyncHBaseAdmin rawAdmin = (RawAsyncHBaseAdmin) ASYNC_CONN.getAdmin(); 071 TEST_UTIL.createMultiRegionTable(tableName, HConstants.CATALOG_FAMILY); 072 AsyncTableRegionLocator locator = ASYNC_CONN.getRegionLocator(tableName); 073 HRegionLocation regionLocation = locator.getRegionLocation(Bytes.toBytes("mmm")).get(); 074 RegionInfo region = regionLocation.getRegion(); 075 byte[] regionName = regionLocation.getRegion().getRegionName(); 076 HRegionLocation location = rawAdmin.getRegionLocation(regionName).get(); 077 assertTrue(Bytes.equals(regionName, location.getRegion().getRegionName())); 078 location = rawAdmin.getRegionLocation(region.getEncodedNameAsBytes()).get(); 079 assertTrue(Bytes.equals(regionName, location.getRegion().getRegionName())); 080 } 081 082 @Test 083 public void testSplitSwitch() throws Exception { 084 createTableWithDefaultConf(tableName); 085 byte[][] families = { FAMILY }; 086 final int rows = 10000; 087 TestAsyncRegionAdminApi.loadData(tableName, families, rows); 088 089 AsyncTable<AdvancedScanResultConsumer> metaTable = ASYNC_CONN.getTable(META_TABLE_NAME); 090 List<HRegionLocation> regionLocations = 091 ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); 092 int originalCount = regionLocations.size(); 093 094 initSplitMergeSwitch(); 095 assertTrue(admin.splitSwitch(false).get()); 096 try { 097 admin.split(tableName, Bytes.toBytes(rows / 2)).join(); 098 } catch (Exception e) { 099 // Expected 100 } 101 int count = admin.getRegions(tableName).get().size(); 102 assertTrue(originalCount == count); 103 104 assertFalse(admin.splitSwitch(true).get()); 105 admin.split(tableName).join(); 106 while ((count = admin.getRegions(tableName).get().size()) == originalCount) { 107 Threads.sleep(100); 108 } 109 assertTrue(originalCount < count); 110 } 111 112 @Test 113 @Ignore 114 // It was ignored in TestSplitOrMergeStatus, too 115 public void testMergeSwitch() throws Exception { 116 createTableWithDefaultConf(tableName); 117 byte[][] families = { FAMILY }; 118 TestAsyncRegionAdminApi.loadData(tableName, families, 1000); 119 120 AsyncTable<AdvancedScanResultConsumer> metaTable = ASYNC_CONN.getTable(META_TABLE_NAME); 121 List<HRegionLocation> regionLocations = 122 ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); 123 int originalCount = regionLocations.size(); 124 125 initSplitMergeSwitch(); 126 admin.split(tableName).join(); 127 int postSplitCount = originalCount; 128 while ((postSplitCount = admin.getRegions(tableName).get().size()) == originalCount) { 129 Threads.sleep(100); 130 } 131 assertTrue("originalCount=" + originalCount + ", postSplitCount=" + postSplitCount, 132 originalCount != postSplitCount); 133 134 // Merge switch is off so merge should NOT succeed. 135 assertTrue(admin.mergeSwitch(false).get()); 136 List<RegionInfo> regions = admin.getRegions(tableName).get(); 137 assertTrue(regions.size() > 1); 138 admin.mergeRegions(regions.get(0).getRegionName(), regions.get(1).getRegionName(), true).join(); 139 int count = admin.getRegions(tableName).get().size(); 140 assertTrue("postSplitCount=" + postSplitCount + ", count=" + count, postSplitCount == count); 141 142 // Merge switch is on so merge should succeed. 143 assertFalse(admin.mergeSwitch(true).get()); 144 admin.mergeRegions(regions.get(0).getRegionName(), regions.get(1).getRegionName(), true).join(); 145 count = admin.getRegions(tableName).get().size(); 146 assertTrue((postSplitCount / 2) == count); 147 } 148 149 private void initSplitMergeSwitch() throws Exception { 150 if (!admin.isSplitEnabled().get()) { 151 admin.splitSwitch(true).get(); 152 } 153 if (!admin.isMergeEnabled().get()) { 154 admin.mergeSwitch(true).get(); 155 } 156 assertTrue(admin.isSplitEnabled().get()); 157 assertTrue(admin.isMergeEnabled().get()); 158 } 159 160 @Test 161 public void testMergeRegions() throws Exception { 162 byte[][] splitRows = new byte[][] { Bytes.toBytes("3"), Bytes.toBytes("6") }; 163 createTableWithDefaultConf(tableName, splitRows); 164 165 AsyncTable<AdvancedScanResultConsumer> metaTable = ASYNC_CONN.getTable(META_TABLE_NAME); 166 List<HRegionLocation> regionLocations = 167 ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); 168 RegionInfo regionA; 169 RegionInfo regionB; 170 RegionInfo regionC; 171 RegionInfo mergedChildRegion = null; 172 173 // merge with full name 174 assertEquals(3, regionLocations.size()); 175 regionA = regionLocations.get(0).getRegion(); 176 regionB = regionLocations.get(1).getRegion(); 177 regionC = regionLocations.get(2).getRegion(); 178 admin.mergeRegions(regionA.getRegionName(), regionB.getRegionName(), false).get(); 179 180 regionLocations = ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); 181 182 assertEquals(2, regionLocations.size()); 183 for (HRegionLocation rl : regionLocations) { 184 if (regionC.compareTo(rl.getRegion()) != 0) { 185 mergedChildRegion = rl.getRegion(); 186 break; 187 } 188 } 189 190 assertNotNull(mergedChildRegion); 191 // Need to wait GC for merged child region is done. 192 HMaster services = TEST_UTIL.getHBaseCluster().getMaster(); 193 CatalogJanitor cj = services.getCatalogJanitor(); 194 assertTrue(cj.scan() > 0); 195 // Wait until all procedures settled down 196 while (!services.getMasterProcedureExecutor().getActiveProcIds().isEmpty()) { 197 Thread.sleep(200); 198 } 199 // merge with encoded name 200 admin.mergeRegions(regionC.getRegionName(), mergedChildRegion.getRegionName(), false).get(); 201 202 regionLocations = ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); 203 assertEquals(1, regionLocations.size()); 204 } 205 206 @Test 207 public void testMergeRegionsInvalidRegionCount() throws Exception { 208 byte[][] splitRows = new byte[][] { Bytes.toBytes("3"), Bytes.toBytes("6") }; 209 createTableWithDefaultConf(tableName, splitRows); 210 List<RegionInfo> regions = admin.getRegions(tableName).join(); 211 // 0 212 try { 213 admin.mergeRegions(Collections.emptyList(), false).get(); 214 fail(); 215 } catch (ExecutionException e) { 216 // expected 217 assertThat(e.getCause(), instanceOf(IllegalArgumentException.class)); 218 } 219 // 1 220 try { 221 admin.mergeRegions(regions.stream().limit(1).map(RegionInfo::getEncodedNameAsBytes) 222 .collect(Collectors.toList()), false).get(); 223 fail(); 224 } catch (ExecutionException e) { 225 // expected 226 assertThat(e.getCause(), instanceOf(IllegalArgumentException.class)); 227 } 228 } 229 230 @Test 231 public void testSplitTable() throws Exception { 232 initSplitMergeSwitch(); 233 splitTest(TableName.valueOf("testSplitTable"), 3000, false, null); 234 splitTest(TableName.valueOf("testSplitTableWithSplitPoint"), 3000, false, Bytes.toBytes("3")); 235 splitTest(TableName.valueOf("testSplitTableRegion"), 3000, true, null); 236 splitTest(TableName.valueOf("testSplitTableRegionWithSplitPoint2"), 3000, true, 237 Bytes.toBytes("3")); 238 } 239 240 private void splitTest(TableName tableName, int rowCount, boolean isSplitRegion, 241 byte[] splitPoint) throws Exception { 242 // create table 243 createTableWithDefaultConf(tableName); 244 245 AsyncTable<AdvancedScanResultConsumer> metaTable = ASYNC_CONN.getTable(META_TABLE_NAME); 246 List<HRegionLocation> regionLocations = 247 ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); 248 assertEquals(1, regionLocations.size()); 249 250 AsyncTable<?> table = ASYNC_CONN.getTable(tableName); 251 List<Put> puts = new ArrayList<>(); 252 for (int i = 0; i < rowCount; i++) { 253 Put put = new Put(Bytes.toBytes(i)); 254 put.addColumn(FAMILY, null, Bytes.toBytes("value" + i)); 255 puts.add(put); 256 } 257 table.putAll(puts).join(); 258 259 if (isSplitRegion) { 260 if (splitPoint == null) { 261 admin.splitRegion(regionLocations.get(0).getRegion().getRegionName()).get(); 262 } else { 263 admin.splitRegion(regionLocations.get(0).getRegion().getRegionName(), splitPoint).get(); 264 } 265 } else { 266 if (splitPoint == null) { 267 admin.split(tableName).get(); 268 } else { 269 admin.split(tableName, splitPoint).get(); 270 } 271 } 272 273 int count = 0; 274 for (int i = 0; i < 45; i++) { 275 try { 276 regionLocations = 277 ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); 278 count = regionLocations.size(); 279 if (count >= 2) { 280 break; 281 } 282 Thread.sleep(1000L); 283 } catch (Exception e) { 284 LOG.error(e.toString(), e); 285 } 286 } 287 assertEquals(2, count); 288 } 289 290 @Test 291 public void testTruncateRegion() throws Exception { 292 // Arrange - Create table, insert data, identify region to truncate. 293 final byte[][] splitKeys = 294 new byte[][] { Bytes.toBytes("30"), Bytes.toBytes("60"), Bytes.toBytes("90") }; 295 String family1 = "f1"; 296 String family2 = "f2"; 297 298 final String[] sFamilies = new String[] { family1, family2 }; 299 final byte[][] bFamilies = new byte[][] { Bytes.toBytes(family1), Bytes.toBytes(family2) }; 300 createTableWithDefaultConf(tableName, splitKeys, bFamilies); 301 302 AsyncTable<AdvancedScanResultConsumer> metaTable = ASYNC_CONN.getTable(META_TABLE_NAME); 303 List<HRegionLocation> regionLocations = 304 ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); 305 RegionInfo regionToBeTruncated = regionLocations.get(0).getRegion(); 306 307 assertEquals(4, regionLocations.size()); 308 309 AssignmentTestingUtil.insertData(TEST_UTIL, tableName, 2, 21, sFamilies); 310 AssignmentTestingUtil.insertData(TEST_UTIL, tableName, 2, 31, sFamilies); 311 AssignmentTestingUtil.insertData(TEST_UTIL, tableName, 2, 61, sFamilies); 312 AssignmentTestingUtil.insertData(TEST_UTIL, tableName, 2, 91, sFamilies); 313 int rowCountBeforeTruncate = TEST_UTIL.countRows(tableName); 314 315 // Act - Truncate the first region 316 admin.truncateRegion(regionToBeTruncated.getRegionName()).get(); 317 318 // Assert 319 int rowCountAfterTruncate = TEST_UTIL.countRows(tableName); 320 assertNotEquals(rowCountBeforeTruncate, rowCountAfterTruncate); 321 int expectedRowCount = rowCountBeforeTruncate - 2;// Since region with 2 rows was truncated. 322 assertEquals(expectedRowCount, rowCountAfterTruncate); 323 } 324 325 @Test 326 public void testTruncateReplicaRegionNotAllowed() throws Exception { 327 // Arrange - Create table, insert data, identify region to truncate. 328 final byte[][] splitKeys = 329 new byte[][] { Bytes.toBytes("30"), Bytes.toBytes("60"), Bytes.toBytes("90") }; 330 String family1 = "f1"; 331 String family2 = "f2"; 332 333 final byte[][] bFamilies = new byte[][] { Bytes.toBytes(family1), Bytes.toBytes(family2) }; 334 createTableWithDefaultConf(tableName, 2, splitKeys, bFamilies); 335 336 AsyncTable<AdvancedScanResultConsumer> metaTable = ASYNC_CONN.getTable(META_TABLE_NAME); 337 List<HRegionLocation> regionLocations = 338 ClientMetaTableAccessor.getTableHRegionLocations(metaTable, tableName).get(); 339 RegionInfo primaryRegion = regionLocations.get(0).getRegion(); 340 341 RegionInfo firstReplica = RegionReplicaUtil.getRegionInfoForReplica(primaryRegion, 1); 342 343 // Act - Truncate the first region 344 try { 345 admin.truncateRegion(firstReplica.getRegionName()).get(); 346 } catch (Exception e) { 347 // Assert 348 assertEquals("Expected message is different", 349 "Can't truncate replicas directly.Replicas are auto-truncated " 350 + "when their primary is truncated.", 351 e.getCause().getMessage()); 352 } 353 } 354 355 @Test 356 public void testTruncateRegionsMetaTableRegionsNotAllowed() throws Exception { 357 AsyncTableRegionLocator locator = ASYNC_CONN.getRegionLocator(META_TABLE_NAME); 358 List<HRegionLocation> regionLocations = locator.getAllRegionLocations().get(); 359 HRegionLocation regionToBeTruncated = regionLocations.get(0); 360 // 1 361 try { 362 admin.truncateRegion(regionToBeTruncated.getRegion().getRegionName()).get(); 363 fail(); 364 } catch (ExecutionException e) { 365 // expected 366 assertThat(e.getCause(), instanceOf(IOException.class)); 367 assertEquals("Can't truncate region in catalog tables", e.getCause().getMessage()); 368 } 369 } 370}