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