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.master.procedure; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023 024import java.io.IOException; 025import org.apache.hadoop.hbase.ConcurrentTableModificationException; 026import org.apache.hadoop.hbase.DoNotRetryIOException; 027import org.apache.hadoop.hbase.HBaseClassTestRule; 028import org.apache.hadoop.hbase.HBaseIOException; 029import org.apache.hadoop.hbase.HColumnDescriptor; 030import org.apache.hadoop.hbase.HTableDescriptor; 031import org.apache.hadoop.hbase.InvalidFamilyOperationException; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 035import org.apache.hadoop.hbase.client.CoprocessorDescriptorBuilder; 036import org.apache.hadoop.hbase.client.PerClientRandomNonceGenerator; 037import org.apache.hadoop.hbase.client.RegionInfo; 038import org.apache.hadoop.hbase.client.TableDescriptor; 039import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 040import org.apache.hadoop.hbase.io.compress.Compression; 041import org.apache.hadoop.hbase.master.procedure.MasterProcedureTestingUtility.StepHook; 042import org.apache.hadoop.hbase.procedure2.Procedure; 043import org.apache.hadoop.hbase.procedure2.ProcedureExecutor; 044import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; 045import org.apache.hadoop.hbase.regionserver.HRegion; 046import org.apache.hadoop.hbase.testclassification.LargeTests; 047import org.apache.hadoop.hbase.testclassification.MasterTests; 048import org.apache.hadoop.hbase.util.Bytes; 049import org.apache.hadoop.hbase.util.NonceKey; 050import org.apache.hadoop.hbase.util.TableDescriptorChecker; 051import org.junit.Assert; 052import org.junit.ClassRule; 053import org.junit.Rule; 054import org.junit.Test; 055import org.junit.experimental.categories.Category; 056import org.junit.rules.TestName; 057 058@Category({ MasterTests.class, LargeTests.class }) 059public class TestModifyTableProcedure extends TestTableDDLProcedureBase { 060 061 @ClassRule 062 public static final HBaseClassTestRule CLASS_RULE = 063 HBaseClassTestRule.forClass(TestModifyTableProcedure.class); 064 065 @Rule 066 public TestName name = new TestName(); 067 068 private static final String column_Family1 = "cf1"; 069 private static final String column_Family2 = "cf2"; 070 private static final String column_Family3 = "cf3"; 071 072 @Test 073 public void testModifyTable() throws Exception { 074 final TableName tableName = TableName.valueOf(name.getMethodName()); 075 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 076 077 MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf"); 078 UTIL.getAdmin().disableTable(tableName); 079 080 // Modify the table descriptor 081 HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName)); 082 083 // Test 1: Modify 1 property 084 long newMaxFileSize = htd.getMaxFileSize() * 2; 085 htd.setMaxFileSize(newMaxFileSize); 086 htd.setRegionReplication(3); 087 088 long procId1 = ProcedureTestingUtility.submitAndWait(procExec, 089 new ModifyTableProcedure(procExec.getEnvironment(), htd)); 090 ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId1)); 091 092 HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName); 093 assertEquals(newMaxFileSize, currentHtd.getMaxFileSize()); 094 095 // Test 2: Modify multiple properties 096 boolean newReadOnlyOption = htd.isReadOnly() ? false : true; 097 long newMemStoreFlushSize = htd.getMemStoreFlushSize() * 2; 098 htd.setReadOnly(newReadOnlyOption); 099 htd.setMemStoreFlushSize(newMemStoreFlushSize); 100 101 long procId2 = ProcedureTestingUtility.submitAndWait(procExec, 102 new ModifyTableProcedure(procExec.getEnvironment(), htd)); 103 ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2)); 104 105 currentHtd = UTIL.getAdmin().getTableDescriptor(tableName); 106 assertEquals(newReadOnlyOption, currentHtd.isReadOnly()); 107 assertEquals(newMemStoreFlushSize, currentHtd.getMemStoreFlushSize()); 108 } 109 110 @Test 111 public void testModifyTableAddCF() throws Exception { 112 final TableName tableName = TableName.valueOf(name.getMethodName()); 113 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 114 115 MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1"); 116 HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName); 117 assertEquals(1, currentHtd.getFamiliesKeys().size()); 118 119 // Test 1: Modify the table descriptor online 120 String cf2 = "cf2"; 121 HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName)); 122 htd.addFamily(new HColumnDescriptor(cf2)); 123 124 long procId = ProcedureTestingUtility.submitAndWait(procExec, 125 new ModifyTableProcedure(procExec.getEnvironment(), htd)); 126 ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId)); 127 128 currentHtd = UTIL.getAdmin().getTableDescriptor(tableName); 129 assertEquals(2, currentHtd.getFamiliesKeys().size()); 130 assertTrue(currentHtd.hasFamily(Bytes.toBytes(cf2))); 131 132 // Test 2: Modify the table descriptor offline 133 UTIL.getAdmin().disableTable(tableName); 134 ProcedureTestingUtility.waitNoProcedureRunning(procExec); 135 String cf3 = "cf3"; 136 HTableDescriptor htd2 = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName)); 137 htd2.addFamily(new HColumnDescriptor(cf3)); 138 139 long procId2 = ProcedureTestingUtility.submitAndWait(procExec, 140 new ModifyTableProcedure(procExec.getEnvironment(), htd2)); 141 ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2)); 142 143 currentHtd = UTIL.getAdmin().getTableDescriptor(tableName); 144 assertTrue(currentHtd.hasFamily(Bytes.toBytes(cf3))); 145 assertEquals(3, currentHtd.getFamiliesKeys().size()); 146 } 147 148 @Test 149 public void testModifyTableDeleteCF() throws Exception { 150 final TableName tableName = TableName.valueOf(name.getMethodName()); 151 final String cf1 = "cf1"; 152 final String cf2 = "cf2"; 153 final String cf3 = "cf3"; 154 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 155 156 MasterProcedureTestingUtility.createTable(procExec, tableName, null, cf1, cf2, cf3); 157 HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName); 158 assertEquals(3, currentHtd.getFamiliesKeys().size()); 159 160 // Test 1: Modify the table descriptor 161 HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName)); 162 htd.removeFamily(Bytes.toBytes(cf2)); 163 164 long procId = ProcedureTestingUtility.submitAndWait(procExec, 165 new ModifyTableProcedure(procExec.getEnvironment(), htd)); 166 ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId)); 167 168 currentHtd = UTIL.getAdmin().getTableDescriptor(tableName); 169 assertEquals(2, currentHtd.getFamiliesKeys().size()); 170 assertFalse(currentHtd.hasFamily(Bytes.toBytes(cf2))); 171 172 // Test 2: Modify the table descriptor offline 173 UTIL.getAdmin().disableTable(tableName); 174 ProcedureTestingUtility.waitNoProcedureRunning(procExec); 175 176 HTableDescriptor htd2 = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName)); 177 htd2.removeFamily(Bytes.toBytes(cf3)); 178 // Disable Sanity check 179 htd2.setConfiguration(TableDescriptorChecker.TABLE_SANITY_CHECKS, Boolean.FALSE.toString()); 180 181 long procId2 = ProcedureTestingUtility.submitAndWait(procExec, 182 new ModifyTableProcedure(procExec.getEnvironment(), htd2)); 183 ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2)); 184 185 currentHtd = UTIL.getAdmin().getTableDescriptor(tableName); 186 assertEquals(1, currentHtd.getFamiliesKeys().size()); 187 assertFalse(currentHtd.hasFamily(Bytes.toBytes(cf3))); 188 189 // Removing the last family will fail 190 HTableDescriptor htd3 = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName)); 191 htd3.removeFamily(Bytes.toBytes(cf1)); 192 long procId3 = ProcedureTestingUtility.submitAndWait(procExec, 193 new ModifyTableProcedure(procExec.getEnvironment(), htd3)); 194 final Procedure<?> result = procExec.getResult(procId3); 195 assertEquals(true, result.isFailed()); 196 Throwable cause = ProcedureTestingUtility.getExceptionCause(result); 197 assertTrue("expected DoNotRetryIOException, got " + cause, 198 cause instanceof DoNotRetryIOException); 199 assertEquals(1, currentHtd.getFamiliesKeys().size()); 200 assertTrue(currentHtd.hasFamily(Bytes.toBytes(cf1))); 201 } 202 203 @Test 204 public void testRecoveryAndDoubleExecutionOffline() throws Exception { 205 final TableName tableName = TableName.valueOf(name.getMethodName()); 206 final String cf2 = "cf2"; 207 final String cf3 = "cf3"; 208 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 209 210 // create the table 211 RegionInfo[] regions = 212 MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1", cf3); 213 UTIL.getAdmin().disableTable(tableName); 214 215 ProcedureTestingUtility.waitNoProcedureRunning(procExec); 216 ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true); 217 218 // Modify multiple properties of the table. 219 TableDescriptor oldDescriptor = UTIL.getAdmin().getDescriptor(tableName); 220 TableDescriptor newDescriptor = TableDescriptorBuilder.newBuilder(oldDescriptor) 221 .setCompactionEnabled(!oldDescriptor.isCompactionEnabled()) 222 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(cf2)).removeColumnFamily(Bytes.toBytes(cf3)) 223 .setRegionReplication(3).build(); 224 225 // Start the Modify procedure && kill the executor 226 long procId = 227 procExec.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), newDescriptor)); 228 229 // Restart the executor and execute the step twice 230 MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId); 231 232 // Validate descriptor 233 TableDescriptor currentDescriptor = UTIL.getAdmin().getDescriptor(tableName); 234 assertEquals(newDescriptor.isCompactionEnabled(), currentDescriptor.isCompactionEnabled()); 235 assertEquals(2, newDescriptor.getColumnFamilyNames().size()); 236 237 // cf2 should be added cf3 should be removed 238 MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(), 239 tableName, regions, false, "cf1", cf2); 240 } 241 242 @Test 243 public void testRecoveryAndDoubleExecutionOnline() throws Exception { 244 final TableName tableName = TableName.valueOf(name.getMethodName()); 245 final String cf2 = "cf2"; 246 final String cf3 = "cf3"; 247 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 248 249 // create the table 250 RegionInfo[] regions = 251 MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1", cf3); 252 253 ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true); 254 255 // Modify multiple properties of the table. 256 HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName)); 257 boolean newCompactionEnableOption = htd.isCompactionEnabled() ? false : true; 258 htd.setCompactionEnabled(newCompactionEnableOption); 259 htd.addFamily(new HColumnDescriptor(cf2)); 260 htd.removeFamily(Bytes.toBytes(cf3)); 261 262 // Start the Modify procedure && kill the executor 263 long procId = 264 procExec.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), htd)); 265 266 // Restart the executor and execute the step twice 267 MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId); 268 269 // Validate descriptor 270 HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName); 271 assertEquals(newCompactionEnableOption, currentHtd.isCompactionEnabled()); 272 assertEquals(2, currentHtd.getFamiliesKeys().size()); 273 assertTrue(currentHtd.hasFamily(Bytes.toBytes(cf2))); 274 assertFalse(currentHtd.hasFamily(Bytes.toBytes(cf3))); 275 276 // cf2 should be added cf3 should be removed 277 MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(), 278 tableName, regions, "cf1", cf2); 279 } 280 281 @Test 282 public void testColumnFamilyAdditionTwiceWithNonce() throws Exception { 283 final TableName tableName = TableName.valueOf(name.getMethodName()); 284 final String cf2 = "cf2"; 285 final String cf3 = "cf3"; 286 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 287 288 // create the table 289 RegionInfo[] regions = 290 MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1", cf3); 291 292 ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true); 293 // Modify multiple properties of the table. 294 TableDescriptor td = UTIL.getAdmin().getDescriptor(tableName); 295 TableDescriptor newTd = 296 TableDescriptorBuilder.newBuilder(td).setCompactionEnabled(!td.isCompactionEnabled()) 297 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(cf2)).build(); 298 299 PerClientRandomNonceGenerator nonceGenerator = PerClientRandomNonceGenerator.get(); 300 long nonceGroup = nonceGenerator.getNonceGroup(); 301 long newNonce = nonceGenerator.newNonce(); 302 NonceKey nonceKey = new NonceKey(nonceGroup, newNonce); 303 procExec.registerNonce(nonceKey); 304 305 // Start the Modify procedure && kill the executor 306 final long procId = procExec 307 .submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), newTd), nonceKey); 308 309 // Restart the executor after MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR and try to add column family 310 // as nonce are there , we should not fail 311 MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId, new StepHook() { 312 @Override 313 public boolean execute(int step) throws IOException { 314 if (step == 3) { 315 return procId == UTIL.getHBaseCluster().getMaster().addColumn(tableName, 316 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf2)).build(), nonceGroup, 317 newNonce); 318 } 319 return true; 320 } 321 }); 322 323 // Try with different nonce, now it should fail the checks 324 try { 325 UTIL.getHBaseCluster().getMaster().addColumn(tableName, 326 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf2)).build(), nonceGroup, 327 nonceGenerator.newNonce()); 328 Assert.fail(); 329 } catch (InvalidFamilyOperationException e) { 330 } 331 332 // Validate descriptor 333 TableDescriptor currentHtd = UTIL.getAdmin().getDescriptor(tableName); 334 assertEquals(!td.isCompactionEnabled(), currentHtd.isCompactionEnabled()); 335 assertEquals(3, currentHtd.getColumnFamilyCount()); 336 assertTrue(currentHtd.hasColumnFamily(Bytes.toBytes(cf2))); 337 assertTrue(currentHtd.hasColumnFamily(Bytes.toBytes(cf3))); 338 339 // cf2 should be added 340 MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(), 341 tableName, regions, "cf1", cf2, cf3); 342 } 343 344 @Test 345 public void testRollbackAndDoubleExecutionOnline() throws Exception { 346 final TableName tableName = TableName.valueOf(name.getMethodName()); 347 final String familyName = "cf2"; 348 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 349 350 // create the table 351 RegionInfo[] regions = 352 MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1"); 353 354 ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true); 355 356 TableDescriptor td = UTIL.getAdmin().getDescriptor(tableName); 357 TableDescriptor newTd = 358 TableDescriptorBuilder.newBuilder(td).setCompactionEnabled(!td.isCompactionEnabled()) 359 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(familyName)).build(); 360 361 // Start the Modify procedure && kill the executor 362 long procId = 363 procExec.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), newTd)); 364 365 int lastStep = 8; // failing before MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR 366 MasterProcedureTestingUtility.testRollbackAndDoubleExecution(procExec, procId, lastStep); 367 368 // cf2 should not be present 369 MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(), 370 tableName, regions, "cf1"); 371 } 372 373 @Test 374 public void testRollbackAndDoubleExecutionOffline() throws Exception { 375 final TableName tableName = TableName.valueOf(name.getMethodName()); 376 final String familyName = "cf2"; 377 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 378 379 // create the table 380 RegionInfo[] regions = 381 MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1"); 382 UTIL.getAdmin().disableTable(tableName); 383 384 ProcedureTestingUtility.waitNoProcedureRunning(procExec); 385 ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true); 386 387 TableDescriptor td = UTIL.getAdmin().getDescriptor(tableName); 388 TableDescriptor newTd = 389 TableDescriptorBuilder.newBuilder(td).setCompactionEnabled(!td.isCompactionEnabled()) 390 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(familyName)).setRegionReplication(3) 391 .build(); 392 393 // Start the Modify procedure && kill the executor 394 long procId = 395 procExec.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), newTd)); 396 397 // Restart the executor and rollback the step twice 398 int lastStep = 8; // failing before MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR 399 MasterProcedureTestingUtility.testRollbackAndDoubleExecution(procExec, procId, lastStep); 400 401 // cf2 should not be present 402 MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(), 403 tableName, regions, "cf1"); 404 } 405 406 @Test 407 public void testConcurrentAddColumnFamily() throws IOException, InterruptedException { 408 final TableName tableName = TableName.valueOf(name.getMethodName()); 409 UTIL.createTable(tableName, column_Family1); 410 411 class ConcurrentAddColumnFamily extends Thread { 412 TableName tableName = null; 413 HColumnDescriptor hcd = null; 414 boolean exception; 415 416 public ConcurrentAddColumnFamily(TableName tableName, HColumnDescriptor hcd) { 417 this.tableName = tableName; 418 this.hcd = hcd; 419 this.exception = false; 420 } 421 422 public void run() { 423 try { 424 UTIL.getAdmin().addColumnFamily(tableName, hcd); 425 } catch (Exception e) { 426 if (e.getClass().equals(ConcurrentTableModificationException.class)) { 427 this.exception = true; 428 } 429 } 430 } 431 } 432 ConcurrentAddColumnFamily t1 = 433 new ConcurrentAddColumnFamily(tableName, new HColumnDescriptor(column_Family2)); 434 ConcurrentAddColumnFamily t2 = 435 new ConcurrentAddColumnFamily(tableName, new HColumnDescriptor(column_Family3)); 436 437 t1.start(); 438 t2.start(); 439 440 t1.join(); 441 t2.join(); 442 int noOfColumnFamilies = UTIL.getAdmin().getDescriptor(tableName).getColumnFamilies().length; 443 assertTrue("Expected ConcurrentTableModificationException.", 444 ((t1.exception || t2.exception) && noOfColumnFamilies == 2) || noOfColumnFamilies == 3); 445 } 446 447 @Test 448 public void testConcurrentDeleteColumnFamily() throws IOException, InterruptedException { 449 final TableName tableName = TableName.valueOf(name.getMethodName()); 450 HTableDescriptor htd = new HTableDescriptor(tableName); 451 htd.addFamily(new HColumnDescriptor(column_Family1)); 452 htd.addFamily(new HColumnDescriptor(column_Family2)); 453 htd.addFamily(new HColumnDescriptor(column_Family3)); 454 UTIL.getAdmin().createTable(htd); 455 456 class ConcurrentCreateDeleteTable extends Thread { 457 TableName tableName = null; 458 String columnFamily = null; 459 boolean exception; 460 461 public ConcurrentCreateDeleteTable(TableName tableName, String columnFamily) { 462 this.tableName = tableName; 463 this.columnFamily = columnFamily; 464 this.exception = false; 465 } 466 467 public void run() { 468 try { 469 UTIL.getAdmin().deleteColumnFamily(tableName, columnFamily.getBytes()); 470 } catch (Exception e) { 471 if (e.getClass().equals(ConcurrentTableModificationException.class)) { 472 this.exception = true; 473 } 474 } 475 } 476 } 477 ConcurrentCreateDeleteTable t1 = new ConcurrentCreateDeleteTable(tableName, column_Family2); 478 ConcurrentCreateDeleteTable t2 = new ConcurrentCreateDeleteTable(tableName, column_Family3); 479 480 t1.start(); 481 t2.start(); 482 483 t1.join(); 484 t2.join(); 485 int noOfColumnFamilies = UTIL.getAdmin().getDescriptor(tableName).getColumnFamilies().length; 486 assertTrue("Expected ConcurrentTableModificationException.", 487 ((t1.exception || t2.exception) && noOfColumnFamilies == 2) || noOfColumnFamilies == 1); 488 } 489 490 @Test 491 public void testConcurrentModifyColumnFamily() throws IOException, InterruptedException { 492 final TableName tableName = TableName.valueOf(name.getMethodName()); 493 UTIL.createTable(tableName, column_Family1); 494 495 class ConcurrentModifyColumnFamily extends Thread { 496 TableName tableName = null; 497 ColumnFamilyDescriptor hcd = null; 498 boolean exception; 499 500 public ConcurrentModifyColumnFamily(TableName tableName, ColumnFamilyDescriptor hcd) { 501 this.tableName = tableName; 502 this.hcd = hcd; 503 this.exception = false; 504 } 505 506 public void run() { 507 try { 508 UTIL.getAdmin().modifyColumnFamily(tableName, hcd); 509 } catch (Exception e) { 510 if (e.getClass().equals(ConcurrentTableModificationException.class)) { 511 this.exception = true; 512 } 513 } 514 } 515 } 516 ColumnFamilyDescriptor modColumnFamily1 = 517 ColumnFamilyDescriptorBuilder.newBuilder(column_Family1.getBytes()).setMaxVersions(5).build(); 518 ColumnFamilyDescriptor modColumnFamily2 = 519 ColumnFamilyDescriptorBuilder.newBuilder(column_Family1.getBytes()).setMaxVersions(6).build(); 520 521 ConcurrentModifyColumnFamily t1 = new ConcurrentModifyColumnFamily(tableName, modColumnFamily1); 522 ConcurrentModifyColumnFamily t2 = new ConcurrentModifyColumnFamily(tableName, modColumnFamily2); 523 524 t1.start(); 525 t2.start(); 526 527 t1.join(); 528 t2.join(); 529 530 int maxVersions = UTIL.getAdmin().getDescriptor(tableName) 531 .getColumnFamily(column_Family1.getBytes()).getMaxVersions(); 532 assertTrue("Expected ConcurrentTableModificationException.", (t1.exception && maxVersions == 5) 533 || (t2.exception && maxVersions == 6) || !(t1.exception && t2.exception)); 534 } 535 536 @Test 537 public void testConcurrentModifyTable() throws IOException, InterruptedException { 538 final TableName tableName = TableName.valueOf(name.getMethodName()); 539 UTIL.createTable(tableName, column_Family1); 540 541 class ConcurrentModifyTable extends Thread { 542 TableName tableName = null; 543 TableDescriptor htd = null; 544 boolean exception; 545 546 public ConcurrentModifyTable(TableName tableName, TableDescriptor htd) { 547 this.tableName = tableName; 548 this.htd = htd; 549 this.exception = false; 550 } 551 552 public void run() { 553 try { 554 UTIL.getAdmin().modifyTable(tableName, htd); 555 } catch (Exception e) { 556 if (e.getClass().equals(ConcurrentTableModificationException.class)) { 557 this.exception = true; 558 } 559 } 560 } 561 } 562 TableDescriptor htd = UTIL.getAdmin().getDescriptor(tableName); 563 TableDescriptor modifiedDescriptor = 564 TableDescriptorBuilder.newBuilder(htd).setCompactionEnabled(false).build(); 565 566 ConcurrentModifyTable t1 = new ConcurrentModifyTable(tableName, modifiedDescriptor); 567 ConcurrentModifyTable t2 = new ConcurrentModifyTable(tableName, modifiedDescriptor); 568 569 t1.start(); 570 t2.start(); 571 572 t1.join(); 573 t2.join(); 574 assertFalse("Expected ConcurrentTableModificationException.", (t1.exception || t2.exception)); 575 } 576 577 @Test 578 public void testModifyWillNotReopenRegions() throws IOException { 579 final boolean reopenRegions = false; 580 final TableName tableName = TableName.valueOf(name.getMethodName()); 581 final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor(); 582 583 MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf"); 584 585 // Test 1: Modify table without reopening any regions 586 TableDescriptor htd = UTIL.getAdmin().getDescriptor(tableName); 587 TableDescriptor modifiedDescriptor = TableDescriptorBuilder.newBuilder(htd) 588 .setValue("test" + ".hbase.conf", "test.hbase.conf.value").build(); 589 long procId1 = ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure( 590 procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions)); 591 ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId1)); 592 TableDescriptor currentHtd = UTIL.getAdmin().getDescriptor(tableName); 593 assertEquals("test.hbase.conf.value", currentHtd.getValue("test.hbase.conf")); 594 // Regions should not aware of any changes. 595 for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) { 596 Assert.assertNull(r.getTableDescriptor().getValue("test.hbase.conf")); 597 } 598 // Force regions to reopen 599 for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) { 600 getMaster().getAssignmentManager().move(r.getRegionInfo()); 601 } 602 // After the regions reopen, ensure that the configuration is updated. 603 for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) { 604 assertEquals("test.hbase.conf.value", r.getTableDescriptor().getValue("test.hbase.conf")); 605 } 606 607 // Test 2: Modifying region replication is not allowed 608 htd = UTIL.getAdmin().getDescriptor(tableName); 609 long oldRegionReplication = htd.getRegionReplication(); 610 modifiedDescriptor = TableDescriptorBuilder.newBuilder(htd).setRegionReplication(3).build(); 611 try { 612 ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure( 613 procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions)); 614 Assert.fail( 615 "An exception should have been thrown while modifying region replication properties."); 616 } catch (HBaseIOException e) { 617 assertTrue(e.getMessage().contains("Can not modify")); 618 } 619 currentHtd = UTIL.getAdmin().getDescriptor(tableName); 620 // Nothing changed 621 assertEquals(oldRegionReplication, currentHtd.getRegionReplication()); 622 623 // Test 3: Adding CFs is not allowed 624 htd = UTIL.getAdmin().getDescriptor(tableName); 625 modifiedDescriptor = TableDescriptorBuilder.newBuilder(htd) 626 .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder("NewCF".getBytes()).build()) 627 .build(); 628 try { 629 ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure( 630 procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions)); 631 Assert.fail("Should have thrown an exception while modifying CF!"); 632 } catch (HBaseIOException e) { 633 assertTrue(e.getMessage().contains("Cannot add or remove column families")); 634 } 635 currentHtd = UTIL.getAdmin().getDescriptor(tableName); 636 Assert.assertNull(currentHtd.getColumnFamily("NewCF".getBytes())); 637 638 // Test 4: Modifying CF property is allowed 639 htd = UTIL.getAdmin().getDescriptor(tableName); 640 modifiedDescriptor = 641 TableDescriptorBuilder 642 .newBuilder(htd).modifyColumnFamily(ColumnFamilyDescriptorBuilder 643 .newBuilder("cf".getBytes()).setCompressionType(Compression.Algorithm.SNAPPY).build()) 644 .build(); 645 ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure( 646 procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions)); 647 for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) { 648 Assert.assertEquals(Compression.Algorithm.NONE, 649 r.getTableDescriptor().getColumnFamily("cf".getBytes()).getCompressionType()); 650 } 651 for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) { 652 getMaster().getAssignmentManager().move(r.getRegionInfo()); 653 } 654 for (HRegion r : UTIL.getHBaseCluster().getRegions(tableName)) { 655 Assert.assertEquals(Compression.Algorithm.SNAPPY, 656 r.getTableDescriptor().getColumnFamily("cf".getBytes()).getCompressionType()); 657 } 658 659 // Test 5: Modifying coprocessor is not allowed 660 htd = UTIL.getAdmin().getDescriptor(tableName); 661 modifiedDescriptor = 662 TableDescriptorBuilder.newBuilder(htd).setCoprocessor(CoprocessorDescriptorBuilder 663 .newBuilder("any.coprocessor.name").setJarPath("fake/path").build()).build(); 664 try { 665 ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure( 666 procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions)); 667 Assert.fail("Should have thrown an exception while modifying coprocessor!"); 668 } catch (HBaseIOException e) { 669 assertTrue(e.getMessage().contains("Can not modify Coprocessor")); 670 } 671 currentHtd = UTIL.getAdmin().getDescriptor(tableName); 672 assertEquals(0, currentHtd.getCoprocessorDescriptors().size()); 673 674 // Test 6: Modifying REGION_REPLICATION is not allowed. 675 htd = UTIL.getAdmin().getDescriptor(tableName); 676 modifiedDescriptor = TableDescriptorBuilder.newBuilder(htd).setRegionReplication(3).build(); 677 try { 678 ProcedureTestingUtility.submitAndWait(procExec, new ModifyTableProcedure( 679 procExec.getEnvironment(), modifiedDescriptor, null, htd, false, reopenRegions)); 680 Assert.fail("Should have thrown an exception while modifying REGION_REPLICATION!"); 681 } catch (HBaseIOException e) { 682 System.out.println(e.getMessage()); 683 assertTrue(e.getMessage().contains("Can not modify REGION_REPLICATION")); 684 } 685 } 686}