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;
023import java.io.IOException;
024import org.apache.hadoop.hbase.ConcurrentTableModificationException;
025import org.apache.hadoop.hbase.DoNotRetryIOException;
026import org.apache.hadoop.hbase.HBaseClassTestRule;
027import org.apache.hadoop.hbase.HColumnDescriptor;
028import org.apache.hadoop.hbase.HTableDescriptor;
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.PerClientRandomNonceGenerator;
034import org.apache.hadoop.hbase.client.RegionInfo;
035import org.apache.hadoop.hbase.client.TableDescriptor;
036import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
037import org.apache.hadoop.hbase.master.procedure.MasterProcedureTestingUtility.StepHook;
038import org.apache.hadoop.hbase.procedure2.Procedure;
039import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
040import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
041import org.apache.hadoop.hbase.testclassification.LargeTests;
042import org.apache.hadoop.hbase.testclassification.MasterTests;
043import org.apache.hadoop.hbase.util.Bytes;
044import org.apache.hadoop.hbase.util.NonceKey;
045import org.apache.hadoop.hbase.util.TableDescriptorChecker;
046import org.junit.Assert;
047import org.junit.ClassRule;
048import org.junit.Rule;
049import org.junit.Test;
050import org.junit.experimental.categories.Category;
051import org.junit.rules.TestName;
052
053@Category({MasterTests.class, LargeTests.class})
054public class TestModifyTableProcedure extends TestTableDDLProcedureBase {
055
056  @ClassRule
057  public static final HBaseClassTestRule CLASS_RULE =
058      HBaseClassTestRule.forClass(TestModifyTableProcedure.class);
059
060  @Rule public TestName name = new TestName();
061
062  private static final String column_Family1 = "cf1";
063  private static final String column_Family2 = "cf2";
064  private static final String column_Family3 = "cf3";
065
066  @Test
067  public void testModifyTable() throws Exception {
068    final TableName tableName = TableName.valueOf(name.getMethodName());
069    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
070
071    MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf");
072    UTIL.getAdmin().disableTable(tableName);
073
074    // Modify the table descriptor
075    HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
076
077    // Test 1: Modify 1 property
078    long newMaxFileSize = htd.getMaxFileSize() * 2;
079    htd.setMaxFileSize(newMaxFileSize);
080    htd.setRegionReplication(3);
081
082    long procId1 = ProcedureTestingUtility.submitAndWait(
083        procExec, new ModifyTableProcedure(procExec.getEnvironment(), htd));
084    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId1));
085
086    HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
087    assertEquals(newMaxFileSize, currentHtd.getMaxFileSize());
088
089    // Test 2: Modify multiple properties
090    boolean newReadOnlyOption = htd.isReadOnly() ? false : true;
091    long newMemStoreFlushSize = htd.getMemStoreFlushSize() * 2;
092    htd.setReadOnly(newReadOnlyOption);
093    htd.setMemStoreFlushSize(newMemStoreFlushSize);
094
095    long procId2 = ProcedureTestingUtility.submitAndWait(
096        procExec, new ModifyTableProcedure(procExec.getEnvironment(), htd));
097    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2));
098
099    currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
100    assertEquals(newReadOnlyOption, currentHtd.isReadOnly());
101    assertEquals(newMemStoreFlushSize, currentHtd.getMemStoreFlushSize());
102  }
103
104  @Test
105  public void testModifyTableAddCF() throws Exception {
106    final TableName tableName = TableName.valueOf(name.getMethodName());
107    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
108
109    MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1");
110    HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
111    assertEquals(1, currentHtd.getFamiliesKeys().size());
112
113    // Test 1: Modify the table descriptor online
114    String cf2 = "cf2";
115    HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
116    htd.addFamily(new HColumnDescriptor(cf2));
117
118    long procId = ProcedureTestingUtility.submitAndWait(
119        procExec, new ModifyTableProcedure(procExec.getEnvironment(), htd));
120    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId));
121
122    currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
123    assertEquals(2, currentHtd.getFamiliesKeys().size());
124    assertTrue(currentHtd.hasFamily(Bytes.toBytes(cf2)));
125
126    // Test 2: Modify the table descriptor offline
127    UTIL.getAdmin().disableTable(tableName);
128    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
129    String cf3 = "cf3";
130    HTableDescriptor htd2 =
131        new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
132    htd2.addFamily(new HColumnDescriptor(cf3));
133
134    long procId2 =
135        ProcedureTestingUtility.submitAndWait(procExec,
136          new ModifyTableProcedure(procExec.getEnvironment(), htd2));
137    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2));
138
139    currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
140    assertTrue(currentHtd.hasFamily(Bytes.toBytes(cf3)));
141    assertEquals(3, currentHtd.getFamiliesKeys().size());
142  }
143
144  @Test
145  public void testModifyTableDeleteCF() throws Exception {
146    final TableName tableName = TableName.valueOf(name.getMethodName());
147    final String cf1 = "cf1";
148    final String cf2 = "cf2";
149    final String cf3 = "cf3";
150    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
151
152    MasterProcedureTestingUtility.createTable(procExec, tableName, null, cf1, cf2, cf3);
153    HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
154    assertEquals(3, currentHtd.getFamiliesKeys().size());
155
156    // Test 1: Modify the table descriptor
157    HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
158    htd.removeFamily(Bytes.toBytes(cf2));
159
160    long procId = ProcedureTestingUtility.submitAndWait(
161        procExec, new ModifyTableProcedure(procExec.getEnvironment(), htd));
162    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId));
163
164    currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
165    assertEquals(2, currentHtd.getFamiliesKeys().size());
166    assertFalse(currentHtd.hasFamily(Bytes.toBytes(cf2)));
167
168    // Test 2: Modify the table descriptor offline
169    UTIL.getAdmin().disableTable(tableName);
170    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
171
172    HTableDescriptor htd2 =
173        new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
174    htd2.removeFamily(Bytes.toBytes(cf3));
175    // Disable Sanity check
176    htd2.setConfiguration(TableDescriptorChecker.TABLE_SANITY_CHECKS, Boolean.FALSE.toString());
177
178    long procId2 =
179        ProcedureTestingUtility.submitAndWait(procExec,
180          new ModifyTableProcedure(procExec.getEnvironment(), htd2));
181    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2));
182
183    currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
184    assertEquals(1, currentHtd.getFamiliesKeys().size());
185    assertFalse(currentHtd.hasFamily(Bytes.toBytes(cf3)));
186
187    //Removing the last family will fail
188    HTableDescriptor htd3 =
189        new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
190    htd3.removeFamily(Bytes.toBytes(cf1));
191    long procId3 =
192        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 = MasterProcedureTestingUtility.createTable(
212      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))
223        .removeColumnFamily(Bytes.toBytes(cf3))
224        .setRegionReplication(3)
225        .build();
226
227    // Start the Modify procedure && kill the executor
228    long procId = procExec.submitProcedure(
229      new ModifyTableProcedure(procExec.getEnvironment(), newDescriptor));
230
231    // Restart the executor and execute the step twice
232    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId);
233
234    // Validate descriptor
235    TableDescriptor currentDescriptor = UTIL.getAdmin().getDescriptor(tableName);
236    assertEquals(newDescriptor.isCompactionEnabled(), currentDescriptor.isCompactionEnabled());
237    assertEquals(2, newDescriptor.getColumnFamilyNames().size());
238
239    // cf2 should be added cf3 should be removed
240    MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(),
241      tableName, regions, false, "cf1", cf2);
242  }
243
244  @Test
245  public void testRecoveryAndDoubleExecutionOnline() throws Exception {
246    final TableName tableName = TableName.valueOf(name.getMethodName());
247    final String cf2 = "cf2";
248    final String cf3 = "cf3";
249    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
250
251    // create the table
252    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(
253      procExec, tableName, null, "cf1", cf3);
254
255    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
256
257    // Modify multiple properties of the table.
258    HTableDescriptor htd = new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
259    boolean newCompactionEnableOption = htd.isCompactionEnabled() ? false : true;
260    htd.setCompactionEnabled(newCompactionEnableOption);
261    htd.addFamily(new HColumnDescriptor(cf2));
262    htd.removeFamily(Bytes.toBytes(cf3));
263
264    // Start the Modify procedure && kill the executor
265    long procId = procExec.submitProcedure(
266      new ModifyTableProcedure(procExec.getEnvironment(), htd));
267
268    // Restart the executor and execute the step twice
269    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId);
270
271    // Validate descriptor
272    HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
273    assertEquals(newCompactionEnableOption, currentHtd.isCompactionEnabled());
274    assertEquals(2, currentHtd.getFamiliesKeys().size());
275    assertTrue(currentHtd.hasFamily(Bytes.toBytes(cf2)));
276    assertFalse(currentHtd.hasFamily(Bytes.toBytes(cf3)));
277
278    // cf2 should be added cf3 should be removed
279    MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(),
280      tableName, regions, "cf1", cf2);
281  }
282
283  @Test
284  public void testColumnFamilyAdditionTwiceWithNonce() throws Exception {
285    final TableName tableName = TableName.valueOf(name.getMethodName());
286    final String cf2 = "cf2";
287    final String cf3 = "cf3";
288    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
289
290    // create the table
291    RegionInfo[] regions =
292        MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1", cf3);
293
294    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
295    // Modify multiple properties of the table.
296    TableDescriptor td = UTIL.getAdmin().getDescriptor(tableName);
297    TableDescriptor newTd =
298        TableDescriptorBuilder.newBuilder(td).setCompactionEnabled(!td.isCompactionEnabled())
299          .setColumnFamily(ColumnFamilyDescriptorBuilder.of(cf2)).build();
300
301    PerClientRandomNonceGenerator nonceGenerator = PerClientRandomNonceGenerator.get();
302    long nonceGroup = nonceGenerator.getNonceGroup();
303    long newNonce = nonceGenerator.newNonce();
304    NonceKey nonceKey = new NonceKey(nonceGroup, newNonce);
305    procExec.registerNonce(nonceKey);
306
307    // Start the Modify procedure && kill the executor
308    final long procId = procExec
309        .submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), newTd), nonceKey);
310
311    // Restart the executor after MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR and try to add column family
312    // as nonce are there , we should not fail
313    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId, new StepHook() {
314      @Override
315      public boolean execute(int step) throws IOException {
316        if (step == 3) {
317          return procId == UTIL.getHBaseCluster().getMaster().addColumn(tableName,
318            ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf2)).build(), nonceGroup,
319            newNonce);
320        }
321        return true;
322      }
323    });
324
325    //Try with different nonce, now it should fail the checks
326    try {
327      UTIL.getHBaseCluster().getMaster().addColumn(tableName,
328        ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf2)).build(), nonceGroup,
329        nonceGenerator.newNonce());
330      Assert.fail();
331    } catch (InvalidFamilyOperationException e) {
332    }
333
334    // Validate descriptor
335    TableDescriptor currentHtd = UTIL.getAdmin().getDescriptor(tableName);
336    assertEquals(!td.isCompactionEnabled(), currentHtd.isCompactionEnabled());
337    assertEquals(3, currentHtd.getColumnFamilyCount());
338    assertTrue(currentHtd.hasColumnFamily(Bytes.toBytes(cf2)));
339    assertTrue(currentHtd.hasColumnFamily(Bytes.toBytes(cf3)));
340
341    // cf2 should be added
342    MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(),
343      tableName, regions, "cf1", cf2, cf3);
344  }
345
346  @Test
347  public void testRollbackAndDoubleExecutionOnline() throws Exception {
348    final TableName tableName = TableName.valueOf(name.getMethodName());
349    final String familyName = "cf2";
350    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
351
352    // create the table
353    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(
354      procExec, tableName, null, "cf1");
355
356    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
357
358    TableDescriptor td = UTIL.getAdmin().getDescriptor(tableName);
359    TableDescriptor newTd =
360      TableDescriptorBuilder.newBuilder(td).setCompactionEnabled(!td.isCompactionEnabled())
361        .setColumnFamily(ColumnFamilyDescriptorBuilder.of(familyName)).build();
362
363    // Start the Modify procedure && kill the executor
364    long procId =
365      procExec.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), newTd));
366
367    int lastStep = 3; // failing before MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR
368    MasterProcedureTestingUtility.testRollbackAndDoubleExecution(procExec, procId, lastStep);
369
370    // cf2 should not be present
371    MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(),
372      tableName, regions, "cf1");
373  }
374
375  @Test
376  public void testRollbackAndDoubleExecutionOffline() throws Exception {
377    final TableName tableName = TableName.valueOf(name.getMethodName());
378    final String familyName = "cf2";
379    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
380
381    // create the table
382    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(
383      procExec, tableName, null, "cf1");
384    UTIL.getAdmin().disableTable(tableName);
385
386    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
387    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
388
389    TableDescriptor td = UTIL.getAdmin().getDescriptor(tableName);
390    TableDescriptor newTd =
391      TableDescriptorBuilder.newBuilder(td).setCompactionEnabled(!td.isCompactionEnabled())
392        .setColumnFamily(ColumnFamilyDescriptorBuilder.of(familyName)).setRegionReplication(3)
393        .build();
394
395    // Start the Modify procedure && kill the executor
396    long procId = procExec.submitProcedure(
397      new ModifyTableProcedure(procExec.getEnvironment(), newTd));
398
399    // Restart the executor and rollback the step twice
400    int lastStep = 3; // failing before MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR
401    MasterProcedureTestingUtility.testRollbackAndDoubleExecution(procExec, procId, lastStep);
402
403    // cf2 should not be present
404    MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(),
405      tableName, regions, "cf1");
406  }
407
408  @Test
409  public void testConcurrentAddColumnFamily() throws IOException, InterruptedException {
410    final TableName tableName = TableName.valueOf(name.getMethodName());
411    UTIL.createTable(tableName, column_Family1);
412
413    class ConcurrentAddColumnFamily extends Thread {
414      TableName tableName = null;
415      HColumnDescriptor hcd = null;
416      boolean exception;
417
418      public ConcurrentAddColumnFamily(TableName tableName, HColumnDescriptor hcd) {
419        this.tableName = tableName;
420        this.hcd = hcd;
421        this.exception = false;
422      }
423
424      public void run() {
425        try {
426          UTIL.getAdmin().addColumnFamily(tableName, hcd);
427        } catch (Exception e) {
428          if (e.getClass().equals(ConcurrentTableModificationException.class)) {
429            this.exception = true;
430          }
431        }
432      }
433    }
434    ConcurrentAddColumnFamily t1 =
435        new ConcurrentAddColumnFamily(tableName, new HColumnDescriptor(column_Family2));
436    ConcurrentAddColumnFamily t2 =
437        new ConcurrentAddColumnFamily(tableName, new HColumnDescriptor(column_Family3));
438
439    t1.start();
440    t2.start();
441
442    t1.join();
443    t2.join();
444    int noOfColumnFamilies = UTIL.getAdmin().getDescriptor(tableName).getColumnFamilies().length;
445    assertTrue("Expected ConcurrentTableModificationException.",
446      ((t1.exception || t2.exception) && noOfColumnFamilies == 2) || noOfColumnFamilies == 3);
447  }
448
449  @Test
450  public void testConcurrentDeleteColumnFamily() throws IOException, InterruptedException {
451    final TableName tableName = TableName.valueOf(name.getMethodName());
452    HTableDescriptor htd = new HTableDescriptor(tableName);
453    htd.addFamily(new HColumnDescriptor(column_Family1));
454    htd.addFamily(new HColumnDescriptor(column_Family2));
455    htd.addFamily(new HColumnDescriptor(column_Family3));
456    UTIL.getAdmin().createTable(htd);
457
458    class ConcurrentCreateDeleteTable extends Thread {
459      TableName tableName = null;
460      String columnFamily = null;
461      boolean exception;
462
463      public ConcurrentCreateDeleteTable(TableName tableName, String columnFamily) {
464        this.tableName = tableName;
465        this.columnFamily = columnFamily;
466        this.exception = false;
467      }
468
469      public void run() {
470        try {
471          UTIL.getAdmin().deleteColumnFamily(tableName, columnFamily.getBytes());
472        } catch (Exception e) {
473          if (e.getClass().equals(ConcurrentTableModificationException.class)) {
474            this.exception = true;
475          }
476        }
477      }
478    }
479    ConcurrentCreateDeleteTable t1 = new ConcurrentCreateDeleteTable(tableName, column_Family2);
480    ConcurrentCreateDeleteTable t2 = new ConcurrentCreateDeleteTable(tableName, column_Family3);
481
482    t1.start();
483    t2.start();
484
485    t1.join();
486    t2.join();
487    int noOfColumnFamilies = UTIL.getAdmin().getDescriptor(tableName).getColumnFamilies().length;
488    assertTrue("Expected ConcurrentTableModificationException.",
489      ((t1.exception || t2.exception) && noOfColumnFamilies == 2) || noOfColumnFamilies == 1);
490  }
491
492  @Test
493  public void testConcurrentModifyColumnFamily() throws IOException, InterruptedException {
494    final TableName tableName = TableName.valueOf(name.getMethodName());
495    UTIL.createTable(tableName, column_Family1);
496
497    class ConcurrentModifyColumnFamily extends Thread {
498      TableName tableName = null;
499      ColumnFamilyDescriptor hcd = null;
500      boolean exception;
501
502      public ConcurrentModifyColumnFamily(TableName tableName, ColumnFamilyDescriptor hcd) {
503        this.tableName = tableName;
504        this.hcd = hcd;
505        this.exception = false;
506      }
507
508      public void run() {
509        try {
510          UTIL.getAdmin().modifyColumnFamily(tableName, hcd);
511        } catch (Exception e) {
512          if (e.getClass().equals(ConcurrentTableModificationException.class)) {
513            this.exception = true;
514          }
515        }
516      }
517    }
518    ColumnFamilyDescriptor modColumnFamily1 = ColumnFamilyDescriptorBuilder
519        .newBuilder(column_Family1.getBytes()).setMaxVersions(5).build();
520    ColumnFamilyDescriptor modColumnFamily2 = ColumnFamilyDescriptorBuilder
521        .newBuilder(column_Family1.getBytes()).setMaxVersions(6).build();
522
523    ConcurrentModifyColumnFamily t1 = new ConcurrentModifyColumnFamily(tableName, modColumnFamily1);
524    ConcurrentModifyColumnFamily t2 = new ConcurrentModifyColumnFamily(tableName, modColumnFamily2);
525
526    t1.start();
527    t2.start();
528
529    t1.join();
530    t2.join();
531
532    int maxVersions = UTIL.getAdmin().getDescriptor(tableName)
533        .getColumnFamily(column_Family1.getBytes()).getMaxVersions();
534    assertTrue("Expected ConcurrentTableModificationException.", (t1.exception && maxVersions == 5)
535        || (t2.exception && maxVersions == 6) || !(t1.exception && t2.exception));
536  }
537
538  @Test
539  public void testConcurrentModifyTable() throws IOException, InterruptedException {
540    final TableName tableName = TableName.valueOf(name.getMethodName());
541    UTIL.createTable(tableName, column_Family1);
542
543    class ConcurrentModifyTable extends Thread {
544      TableName tableName = null;
545      TableDescriptor htd = null;
546      boolean exception;
547
548      public ConcurrentModifyTable(TableName tableName, TableDescriptor htd) {
549        this.tableName = tableName;
550        this.htd = htd;
551        this.exception = false;
552      }
553
554      public void run() {
555        try {
556          UTIL.getAdmin().modifyTable(tableName, htd);
557        } catch (Exception e) {
558          if (e.getClass().equals(ConcurrentTableModificationException.class)) {
559            this.exception = true;
560          }
561        }
562      }
563    }
564    TableDescriptor htd = UTIL.getAdmin().getDescriptor(tableName);
565    TableDescriptor modifiedDescriptor =
566        TableDescriptorBuilder.newBuilder(htd).setCompactionEnabled(false).build();
567
568    ConcurrentModifyTable t1 = new ConcurrentModifyTable(tableName, modifiedDescriptor);
569    ConcurrentModifyTable t2 = new ConcurrentModifyTable(tableName, modifiedDescriptor);
570
571    t1.start();
572    t2.start();
573
574    t1.join();
575    t2.join();
576    assertFalse("Expected ConcurrentTableModificationException.", (t1.exception || t2.exception));
577  }
578}