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