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.rsgroup; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023import static org.junit.Assert.fail; 024 025import java.io.IOException; 026import java.util.List; 027import java.util.Optional; 028import java.util.regex.Pattern; 029import org.apache.hadoop.hbase.DoNotRetryIOException; 030import org.apache.hadoop.hbase.HBaseClassTestRule; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.Waiter; 033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 035import org.apache.hadoop.hbase.client.SnapshotDescription; 036import org.apache.hadoop.hbase.client.TableDescriptor; 037import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 038import org.apache.hadoop.hbase.constraint.ConstraintException; 039import org.apache.hadoop.hbase.testclassification.LargeTests; 040import org.apache.hadoop.hbase.util.Bytes; 041import org.junit.After; 042import org.junit.AfterClass; 043import org.junit.Before; 044import org.junit.BeforeClass; 045import org.junit.ClassRule; 046import org.junit.Test; 047import org.junit.experimental.categories.Category; 048 049import org.apache.hbase.thirdparty.com.google.common.collect.Sets; 050 051@Category({ LargeTests.class }) 052public class TestTableDescriptorWithRSGroup extends TestRSGroupsBase { 053 054 @ClassRule 055 public static final HBaseClassTestRule CLASS_RULE = 056 HBaseClassTestRule.forClass(TestTableDescriptorWithRSGroup.class); 057 private final byte[] familyNameBytes = Bytes.toBytes("f1"); 058 059 @BeforeClass 060 public static void setUp() throws Exception { 061 setUpTestBeforeClass(); 062 } 063 064 @AfterClass 065 public static void tearDown() throws Exception { 066 tearDownAfterClass(); 067 } 068 069 @Before 070 public void beforeMethod() throws Exception { 071 setUpBeforeMethod(); 072 } 073 074 @After 075 public void afterMethod() throws Exception { 076 tearDownAfterMethod(); 077 } 078 079 @Test 080 public void testCreateTableInTableDescriptorSpecificRSGroup() throws Exception { 081 final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 1); 082 assertEquals(0, newGroup.getTables().size()); 083 084 // assertion is done in createTableInRSGroup 085 createTableWithRSGroupDetail(newGroup.getName()); 086 087 // Create table should fail if specified rs group does not exist. 088 try { 089 TableDescriptor desc = 090 TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName.getNameAsString() + "_2")) 091 .setRegionServerGroup("nonExistingRSGroup") 092 .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build()) 093 .build(); 094 admin.createTable(desc, getSpitKeys(6)); 095 fail("Should have thrown ConstraintException but no exception thrown."); 096 } catch (ConstraintException e) { 097 assertEquals(e.getMessage(), "Region server group nonExistingRSGroup does not exist."); 098 } 099 } 100 101 private void createTableWithRSGroupDetail(String newGroup) throws Exception { 102 // Create table 103 104 ColumnFamilyDescriptor f1 = ColumnFamilyDescriptorBuilder.newBuilder(familyNameBytes).build(); 105 TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName) 106 .setRegionServerGroup(newGroup).setColumnFamily(f1).build(); 107 admin.createTable(desc, getSpitKeys(5)); 108 109 TEST_UTIL.waitFor(WAIT_TIMEOUT, (Waiter.Predicate<Exception>) () -> { 110 List<String> regions = getTableRegionMap().get(tableName); 111 if (regions == null) { 112 return false; 113 } 114 115 return getTableRegionMap().get(tableName).size() >= 5; 116 }); 117 TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor(); 118 Optional<String> regionServerGroup = descriptor.getRegionServerGroup(); 119 assertTrue("RSGroup info is not updated into TableDescriptor when table is created.", 120 regionServerGroup.isPresent()); 121 assertEquals(newGroup, regionServerGroup.get()); 122 } 123 124 // moveTables should update rs group info in table descriptor 125 @Test 126 public void testMoveTablesShouldUpdateTableDescriptor() throws Exception { 127 RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1); 128 assertEquals(0, rsGroup1.getTables().size()); 129 130 createTableWithRSGroupDetail(rsGroup1.getName()); 131 TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor(); 132 Optional<String> regionServerGroup = descriptor.getRegionServerGroup(); 133 assertTrue("RSGroup info is not updated into TableDescriptor when table created", 134 regionServerGroup.isPresent()); 135 assertEquals(rsGroup1.getName(), regionServerGroup.get()); 136 137 // moveTables 138 RSGroupInfo rsGroup2 = addGroup("rsGroup2", 1); 139 rsGroupAdmin.moveTables(Sets.newHashSet(tableName), rsGroup2.getName()); 140 descriptor = admin.getConnection().getTable(tableName).getDescriptor(); 141 regionServerGroup = descriptor.getRegionServerGroup(); 142 assertTrue("RSGroup info is not updated into TableDescriptor when table moved", 143 regionServerGroup.isPresent()); 144 assertEquals(rsGroup2.getName(), regionServerGroup.get()); 145 } 146 147 // moveServersAndTables should update rs group info in table descriptor 148 @Test 149 public void testMoveServersAndTablesShouldUpdateTableDescriptor() throws Exception { 150 RSGroupInfo rsGroup1 = addGroup("rsGroup1", 2); 151 assertEquals(0, rsGroup1.getTables().size()); 152 153 createTableWithRSGroupDetail(rsGroup1.getName()); 154 TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor(); 155 Optional<String> regionServerGroup = descriptor.getRegionServerGroup(); 156 assertTrue("RSGroup info is not updated into TableDescriptor when table created", 157 regionServerGroup.isPresent()); 158 assertEquals(rsGroup1.getName(), regionServerGroup.get()); 159 160 // moveServersAndTables 161 rsGroupAdmin.moveServersAndTables(Sets.newHashSet(rsGroup1.getServers().iterator().next()), 162 Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP); 163 164 descriptor = admin.getConnection().getTable(tableName).getDescriptor(); 165 regionServerGroup = descriptor.getRegionServerGroup(); 166 assertTrue("RSGroup info is not updated into TableDescriptor when table moved", 167 regionServerGroup.isPresent()); 168 assertEquals(RSGroupInfo.DEFAULT_GROUP, regionServerGroup.get()); 169 170 } 171 172 @Test 173 public void testRenameRSGroupUpdatesTableDescriptor() throws Exception { 174 RSGroupInfo oldGroup = addGroup("oldGroup", 1); 175 assertEquals(0, oldGroup.getTables().size()); 176 177 createTableWithRSGroupDetail(oldGroup.getName()); 178 179 final String newGroupName = "newGroup"; 180 rsGroupAdmin.renameRSGroup(oldGroup.getName(), newGroupName); 181 TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor(); 182 Optional<String> regionServerGroup = descriptor.getRegionServerGroup(); 183 assertTrue("RSGroup info is not updated into TableDescriptor when rs group renamed", 184 regionServerGroup.isPresent()); 185 assertEquals(newGroupName, regionServerGroup.get()); 186 } 187 188 @Test 189 public void testCloneSnapshotWithRSGroup() throws Exception { 190 RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1); 191 assertEquals(0, rsGroup1.getTables().size()); 192 // Creates table in rsGroup1 193 createTableWithRSGroupDetail(rsGroup1.getName()); 194 195 // Create snapshot 196 final String snapshotName = "snapShot2"; 197 admin.snapshot(snapshotName, tableName); 198 final List<SnapshotDescription> snapshotDescriptions = 199 admin.listSnapshots(Pattern.compile("snapShot2")); 200 assertEquals(1, snapshotDescriptions.size()); 201 assertEquals(snapshotName, snapshotDescriptions.get(0).getName()); 202 203 // Move table to different rs group then delete the old rs group 204 RSGroupInfo rsGroup2 = addGroup("rsGroup2", 1); 205 rsGroupAdmin.moveTables(Sets.newHashSet(tableName), rsGroup2.getName()); 206 rsGroup2 = rsGroupAdmin.getRSGroupInfo(rsGroup2.getName()); 207 assertEquals(1, rsGroup2.getTables().size()); 208 assertEquals(rsGroup2.getTables().first(), tableName); 209 210 // Clone Snapshot 211 final TableName clonedTable1 = TableName.valueOf(tableName.getNameAsString() + "_1"); 212 admin.cloneSnapshot(Bytes.toBytes(snapshotName), clonedTable1); 213 214 // Verify that cloned table is created into old rs group 215 final RSGroupInfo rsGroupInfoOfTable = rsGroupAdmin.getRSGroupInfoOfTable(clonedTable1); 216 assertEquals(rsGroup1.getName(), rsGroupInfoOfTable.getName()); 217 TableDescriptor descriptor = admin.getConnection().getTable(clonedTable1).getDescriptor(); 218 Optional<String> regionServerGroup = descriptor.getRegionServerGroup(); 219 assertTrue("RSGroup info is not updated into TableDescriptor when table is cloned.", 220 regionServerGroup.isPresent()); 221 assertEquals(rsGroup1.getName(), regionServerGroup.get()); 222 223 // Delete table's original rs group, clone should fail. 224 rsGroupAdmin.moveServersAndTables(Sets.newHashSet(rsGroup1.getServers()), 225 Sets.newHashSet(clonedTable1), rsGroup2.getName()); 226 rsGroupAdmin.removeRSGroup(rsGroup1.getName()); 227 // Clone Snapshot 228 final TableName clonedTable2 = TableName.valueOf(tableName.getNameAsString() + "_2"); 229 try { 230 admin.cloneSnapshot(Bytes.toBytes(snapshotName), clonedTable2); 231 fail("Should have thrown ConstraintException but no exception thrown."); 232 } catch (ConstraintException e) { 233 assertTrue( 234 e.getCause().getMessage().contains("Region server group rsGroup1 does not exist.")); 235 } 236 } 237 238 // Modify table should validate rs group existence and move rs group if required 239 @Test 240 public void testMoveTablesWhenModifyTableWithNewRSGroup() throws Exception { 241 RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1); 242 assertEquals(0, rsGroup1.getTables().size()); 243 244 createTableWithRSGroupDetail(rsGroup1.getName()); 245 TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor(); 246 Optional<String> regionServerGroup = descriptor.getRegionServerGroup(); 247 assertTrue("RSGroup info is not updated into TableDescriptor when table created", 248 regionServerGroup.isPresent()); 249 assertEquals(rsGroup1.getName(), regionServerGroup.get()); 250 251 final TableDescriptor newTableDescriptor = 252 TableDescriptorBuilder.newBuilder(descriptor).setRegionServerGroup("rsGroup2").build(); 253 254 // ConstraintException as rsGroup2 does not exits 255 try { 256 admin.modifyTable(newTableDescriptor); 257 fail("Should have thrown ConstraintException but no exception thrown."); 258 } catch (ConstraintException e) { 259 assertTrue( 260 e.getCause().getMessage().contains("Region server group rsGroup2 does not exist.")); 261 } 262 263 addGroup("rsGroup2", 1); 264 // Table creation should be successful as rsGroup2 exists. 265 admin.modifyTable(newTableDescriptor); 266 267 // old group should not have table mapping now 268 rsGroup1 = rsGroupAdmin.getRSGroupInfo("rsGroup1"); 269 assertEquals(0, rsGroup1.getTables().size()); 270 271 RSGroupInfo rsGroup2 = rsGroupAdmin.getRSGroupInfo("rsGroup2"); 272 assertEquals(1, rsGroup2.getTables().size()); 273 descriptor = admin.getConnection().getTable(tableName).getDescriptor(); 274 regionServerGroup = descriptor.getRegionServerGroup(); 275 assertTrue("RSGroup info is not updated into TableDescriptor when table is modified.", 276 regionServerGroup.isPresent()); 277 assertEquals("rsGroup2", regionServerGroup.get()); 278 } 279 280 @Test 281 public void testTableShouldNotAddedIntoRSGroup_WhenModifyTableFails() throws Exception { 282 RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1); 283 assertEquals(0, rsGroup1.getTables().size()); 284 285 createTableWithRSGroupDetail(rsGroup1.getName()); 286 TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor(); 287 288 RSGroupInfo rsGroup2 = addGroup("rsGroup2", 1); 289 final TableDescriptor newTableDescriptor = TableDescriptorBuilder.newBuilder(descriptor) 290 .setRegionServerGroup(rsGroup2.getName()).removeColumnFamily(familyNameBytes).build(); 291 292 // Removed family to fail pre-check validation 293 try { 294 admin.modifyTable(newTableDescriptor); 295 fail("Should have thrown DoNotRetryIOException but no exception thrown."); 296 } catch (DoNotRetryIOException e) { 297 assertTrue( 298 e.getCause().getMessage().contains("Table should have at least one column family")); 299 } 300 rsGroup2 = rsGroupAdmin.getRSGroupInfo(rsGroup2.getName()); 301 assertEquals("Table must not have moved to RSGroup as table modify failed", 0, 302 rsGroup2.getTables().size()); 303 } 304 305 @Test 306 public void testTableShouldNotAddedIntoRSGroup_WhenTableCreationFails() throws Exception { 307 RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1); 308 assertEquals(0, rsGroup1.getTables().size()); 309 310 // Create TableDescriptor without a family so creation fails 311 TableDescriptor desc = 312 TableDescriptorBuilder.newBuilder(tableName).setRegionServerGroup(rsGroup1.getName()).build(); 313 try { 314 admin.createTable(desc, getSpitKeys(5)); 315 fail("Should have thrown DoNotRetryIOException but no exception thrown."); 316 } catch (DoNotRetryIOException e) { 317 assertTrue( 318 e.getCause().getMessage().contains("Table should have at least one column family")); 319 } 320 rsGroup1 = rsGroupAdmin.getRSGroupInfo(rsGroup1.getName()); 321 assertEquals("Table must not have moved to RSGroup as table create operation failed", 0, 322 rsGroup1.getTables().size()); 323 } 324 325 @Test 326 public void testSystemTablesCanBeMovedToNewRSGroupByModifyingTable() throws Exception { 327 RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1); 328 assertEquals(0, rsGroup1.getTables().size()); 329 final TableName tableName = TableName.valueOf("hbase:meta"); 330 final TableDescriptor descriptor = admin.getConnection().getTable(tableName).getDescriptor(); 331 final TableDescriptor newTableDescriptor = TableDescriptorBuilder.newBuilder(descriptor) 332 .setRegionServerGroup(rsGroup1.getName()).build(); 333 admin.modifyTable(newTableDescriptor); 334 final RSGroupInfo rsGroupInfoOfTable = rsGroupAdmin.getRSGroupInfoOfTable(tableName); 335 assertEquals(rsGroup1.getName(), rsGroupInfoOfTable.getName()); 336 } 337 338 @Test 339 public void testUpdateTableDescriptorOnlyIfRSGroupInfoWasStoredInTableDescriptor() 340 throws Exception { 341 RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1); 342 assertEquals(0, rsGroup1.getTables().size()); 343 // create table with rs group info stored in table descriptor 344 createTable(tableName, rsGroup1.getName()); 345 final TableName table2 = TableName.valueOf(tableName.getNameAsString() + "_2"); 346 // create table with no rs group info 347 createTable(table2, null); 348 rsGroupAdmin.moveTables(Sets.newHashSet(tableName), rsGroup1.getName()); 349 assertTrue("RSGroup info is not updated into TableDescriptor when table created", 350 admin.getConnection().getTable(tableName).getDescriptor().getRegionServerGroup().isPresent()); 351 assertFalse( 352 "Table descriptor should not have been updated " 353 + "as rs group info was not stored in table descriptor.", 354 admin.getConnection().getTable(table2).getDescriptor().getRegionServerGroup().isPresent()); 355 356 final String rsGroup2 = "rsGroup2"; 357 rsGroupAdmin.renameRSGroup(rsGroup1.getName(), rsGroup2); 358 assertEquals(rsGroup2, 359 admin.getConnection().getTable(tableName).getDescriptor().getRegionServerGroup().get()); 360 assertFalse( 361 "Table descriptor should not have been updated " 362 + "as rs group info was not stored in table descriptor.", 363 admin.getConnection().getTable(table2).getDescriptor().getRegionServerGroup().isPresent()); 364 } 365 366 @Test 367 public void testModifyAndMoveTableScenario() throws Exception { 368 RSGroupInfo rsGroup1 = addGroup("rsGroup1", 1); 369 assertEquals(0, rsGroup1.getTables().size()); 370 // create table with rs group info stored in table descriptor 371 createTable(tableName, rsGroup1.getName()); 372 final TableName table2 = TableName.valueOf(tableName.getNameAsString() + "_2"); 373 // create table with no rs group info 374 createTable(table2, null); 375 rsGroupAdmin.moveTables(Sets.newHashSet(table2), rsGroup1.getName()); 376 377 RSGroupInfo rsGroup2 = addGroup("rsGroup2", 1); 378 rsGroupAdmin.moveTables(Sets.newHashSet(tableName, table2), rsGroup2.getName()); 379 rsGroup2 = rsGroupAdmin.getRSGroupInfo(rsGroup2.getName()); 380 assertEquals("Table movement failed.", 2, rsGroup2.getTables().size()); 381 } 382 383 private void createTable(TableName tName, String rsGroupName) throws Exception { 384 ColumnFamilyDescriptor f1 = ColumnFamilyDescriptorBuilder.newBuilder(familyNameBytes).build(); 385 final TableDescriptorBuilder builder = 386 TableDescriptorBuilder.newBuilder(tName).setColumnFamily(f1); 387 if (rsGroupName != null) { 388 builder.setRegionServerGroup(rsGroupName); 389 } 390 TableDescriptor desc = builder.build(); 391 admin.createTable(desc, getSpitKeys(10)); 392 TEST_UTIL.waitFor(WAIT_TIMEOUT, (Waiter.Predicate<Exception>) () -> { 393 List<String> regions = getTableRegionMap().get(tName); 394 if (regions == null) { 395 return false; 396 } 397 398 return getTableRegionMap().get(tName).size() >= 5; 399 }); 400 } 401 402 private byte[][] getSpitKeys(int numRegions) throws IOException { 403 if (numRegions < 3) { 404 throw new IOException("Must create at least 3 regions"); 405 } 406 byte[] startKey = Bytes.toBytes("aaaaa"); 407 byte[] endKey = Bytes.toBytes("zzzzz"); 408 return Bytes.split(startKey, endKey, numRegions - 3); 409 } 410}