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.InvalidFamilyOperationException;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
031import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
032import org.apache.hadoop.hbase.client.PerClientRandomNonceGenerator;
033import org.apache.hadoop.hbase.client.RegionInfo;
034import org.apache.hadoop.hbase.client.TableDescriptor;
035import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
036import org.apache.hadoop.hbase.master.procedure.MasterProcedureTestingUtility.StepHook;
037import org.apache.hadoop.hbase.procedure2.Procedure;
038import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
039import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
040import org.apache.hadoop.hbase.testclassification.LargeTests;
041import org.apache.hadoop.hbase.testclassification.MasterTests;
042import org.apache.hadoop.hbase.util.Bytes;
043import org.apache.hadoop.hbase.util.NonceKey;
044import org.apache.hadoop.hbase.util.TableDescriptorChecker;
045import org.junit.Assert;
046import org.junit.ClassRule;
047import org.junit.Rule;
048import org.junit.Test;
049import org.junit.experimental.categories.Category;
050import org.junit.rules.TestName;
051
052@Category({ MasterTests.class, LargeTests.class })
053public class TestModifyTableProcedure extends TestTableDDLProcedureBase {
054
055  @ClassRule
056  public static final HBaseClassTestRule CLASS_RULE =
057    HBaseClassTestRule.forClass(TestModifyTableProcedure.class);
058
059  @Rule
060  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    TableDescriptor htd = UTIL.getAdmin().getDescriptor(tableName);
076
077    // Test 1: Modify 1 property
078    long newMaxFileSize = htd.getMaxFileSize() * 2;
079    htd = TableDescriptorBuilder.newBuilder(htd).setMaxFileSize(newMaxFileSize)
080      .setRegionReplication(3).build();
081
082    long procId1 = ProcedureTestingUtility.submitAndWait(procExec,
083      new ModifyTableProcedure(procExec.getEnvironment(), htd));
084    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId1));
085
086    TableDescriptor currentHtd = UTIL.getAdmin().getDescriptor(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 = TableDescriptorBuilder.newBuilder(htd).setReadOnly(newReadOnlyOption)
093      .setMemStoreFlushSize(newMemStoreFlushSize).build();
094
095    long procId2 = ProcedureTestingUtility.submitAndWait(procExec,
096      new ModifyTableProcedure(procExec.getEnvironment(), htd));
097    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2));
098
099    currentHtd = UTIL.getAdmin().getDescriptor(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    TableDescriptor currentHtd = UTIL.getAdmin().getDescriptor(tableName);
111    assertEquals(1, currentHtd.getColumnFamilyNames().size());
112
113    // Test 1: Modify the table descriptor online
114    String cf2 = "cf2";
115    TableDescriptorBuilder tableDescriptorBuilder =
116      TableDescriptorBuilder.newBuilder(UTIL.getAdmin().getDescriptor(tableName));
117    ColumnFamilyDescriptor columnFamilyDescriptor =
118      ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf2)).build();
119    tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
120
121    long procId = ProcedureTestingUtility.submitAndWait(procExec,
122      new ModifyTableProcedure(procExec.getEnvironment(), tableDescriptorBuilder.build()));
123    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId));
124
125    currentHtd = UTIL.getAdmin().getDescriptor(tableName);
126    assertEquals(2, currentHtd.getColumnFamilyNames().size());
127    assertTrue(currentHtd.hasColumnFamily(Bytes.toBytes(cf2)));
128
129    // Test 2: Modify the table descriptor offline
130    UTIL.getAdmin().disableTable(tableName);
131    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
132    String cf3 = "cf3";
133    tableDescriptorBuilder =
134      TableDescriptorBuilder.newBuilder(UTIL.getAdmin().getDescriptor(tableName));
135    columnFamilyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf3)).build();
136    tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
137
138    long procId2 = ProcedureTestingUtility.submitAndWait(procExec,
139      new ModifyTableProcedure(procExec.getEnvironment(), tableDescriptorBuilder.build()));
140    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2));
141
142    currentHtd = UTIL.getAdmin().getDescriptor(tableName);
143    assertTrue(currentHtd.hasColumnFamily(Bytes.toBytes(cf3)));
144    assertEquals(3, currentHtd.getColumnFamilyNames().size());
145  }
146
147  @Test
148  public void testModifyTableDeleteCF() throws Exception {
149    final TableName tableName = TableName.valueOf(name.getMethodName());
150    final String cf1 = "cf1";
151    final String cf2 = "cf2";
152    final String cf3 = "cf3";
153    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
154
155    MasterProcedureTestingUtility.createTable(procExec, tableName, null, cf1, cf2, cf3);
156    TableDescriptor currentHtd = UTIL.getAdmin().getDescriptor(tableName);
157    assertEquals(3, currentHtd.getColumnFamilyNames().size());
158
159    // Test 1: Modify the table descriptor
160    TableDescriptor htd = UTIL.getAdmin().getDescriptor(tableName);
161    htd = TableDescriptorBuilder.newBuilder(htd).removeColumnFamily(Bytes.toBytes(cf2)).build();
162
163    long procId = ProcedureTestingUtility.submitAndWait(procExec,
164      new ModifyTableProcedure(procExec.getEnvironment(), htd));
165    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId));
166
167    currentHtd = UTIL.getAdmin().getDescriptor(tableName);
168    assertEquals(2, currentHtd.getColumnFamilyNames().size());
169    assertFalse(currentHtd.hasColumnFamily(Bytes.toBytes(cf2)));
170
171    // Test 2: Modify the table descriptor offline
172    UTIL.getAdmin().disableTable(tableName);
173    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
174
175    TableDescriptor htd2 = UTIL.getAdmin().getDescriptor(tableName);
176    // Disable Sanity check
177    htd2 = TableDescriptorBuilder.newBuilder(htd2).removeColumnFamily(Bytes.toBytes(cf3))
178      .setValue(TableDescriptorChecker.TABLE_SANITY_CHECKS, Boolean.FALSE.toString()).build();
179
180    long procId2 = ProcedureTestingUtility.submitAndWait(procExec,
181      new ModifyTableProcedure(procExec.getEnvironment(), htd2));
182    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2));
183
184    currentHtd = UTIL.getAdmin().getDescriptor(tableName);
185    assertEquals(1, currentHtd.getColumnFamilyNames().size());
186    assertFalse(currentHtd.hasColumnFamily(Bytes.toBytes(cf3)));
187
188    // Removing the last family will fail
189    TableDescriptor htd3 = UTIL.getAdmin().getDescriptor(tableName);
190    htd3 = TableDescriptorBuilder.newBuilder(htd3).removeColumnFamily(Bytes.toBytes(cf1)).build();
191    long procId3 = ProcedureTestingUtility.submitAndWait(procExec,
192      new ModifyTableProcedure(procExec.getEnvironment(), htd3));
193    final Procedure<?> result = procExec.getResult(procId3);
194    assertEquals(true, result.isFailed());
195    Throwable cause = ProcedureTestingUtility.getExceptionCause(result);
196    assertTrue("expected DoNotRetryIOException, got " + cause,
197      cause instanceof DoNotRetryIOException);
198    assertEquals(1, currentHtd.getColumnFamilyNames().size());
199    assertTrue(currentHtd.hasColumnFamily(Bytes.toBytes(cf1)));
200  }
201
202  @Test
203  public void testRecoveryAndDoubleExecutionOffline() throws Exception {
204    final TableName tableName = TableName.valueOf(name.getMethodName());
205    final String cf2 = "cf2";
206    final String cf3 = "cf3";
207    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
208
209    // create the table
210    RegionInfo[] regions =
211      MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1", cf3);
212    UTIL.getAdmin().disableTable(tableName);
213
214    ProcedureTestingUtility.waitNoProcedureRunning(procExec);
215    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
216
217    // Modify multiple properties of the table.
218    TableDescriptor oldDescriptor = UTIL.getAdmin().getDescriptor(tableName);
219    TableDescriptor newDescriptor = TableDescriptorBuilder.newBuilder(oldDescriptor)
220      .setCompactionEnabled(!oldDescriptor.isCompactionEnabled())
221      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(cf2)).removeColumnFamily(Bytes.toBytes(cf3))
222      .setRegionReplication(3).build();
223
224    // Start the Modify procedure && kill the executor
225    long procId =
226      procExec.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), newDescriptor));
227
228    // Restart the executor and execute the step twice
229    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId);
230
231    // Validate descriptor
232    TableDescriptor currentDescriptor = UTIL.getAdmin().getDescriptor(tableName);
233    assertEquals(newDescriptor.isCompactionEnabled(), currentDescriptor.isCompactionEnabled());
234    assertEquals(2, newDescriptor.getColumnFamilyNames().size());
235
236    // cf2 should be added cf3 should be removed
237    MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(),
238      tableName, regions, false, "cf1", cf2);
239  }
240
241  @Test
242  public void testRecoveryAndDoubleExecutionOnline() throws Exception {
243    final TableName tableName = TableName.valueOf(name.getMethodName());
244    final String cf2 = "cf2";
245    final String cf3 = "cf3";
246    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
247
248    // create the table
249    RegionInfo[] regions =
250      MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1", cf3);
251
252    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
253
254    // Modify multiple properties of the table.
255    TableDescriptorBuilder tableDescriptorBuilder =
256      TableDescriptorBuilder.newBuilder(UTIL.getAdmin().getDescriptor(tableName));
257    ColumnFamilyDescriptor columnFamilyDescriptor =
258      ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf2)).build();
259    boolean newCompactionEnableOption = !tableDescriptorBuilder.build().isCompactionEnabled();
260    tableDescriptorBuilder.setCompactionEnabled(newCompactionEnableOption);
261    tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
262    tableDescriptorBuilder.removeColumnFamily(Bytes.toBytes(cf3));
263
264    // Start the Modify procedure && kill the executor
265    long procId = procExec.submitProcedure(
266      new ModifyTableProcedure(procExec.getEnvironment(), tableDescriptorBuilder.build()));
267
268    // Restart the executor and execute the step twice
269    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId);
270
271    // Validate descriptor
272    TableDescriptor currentHtd = UTIL.getAdmin().getDescriptor(tableName);
273    assertEquals(newCompactionEnableOption, currentHtd.isCompactionEnabled());
274    assertEquals(2, currentHtd.getColumnFamilyNames().size());
275    assertTrue(currentHtd.hasColumnFamily(Bytes.toBytes(cf2)));
276    assertFalse(currentHtd.hasColumnFamily(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 =
354      MasterProcedureTestingUtility.createTable(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 = 8; // 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 =
383      MasterProcedureTestingUtility.createTable(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 =
397      procExec.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), newTd));
398
399    // Restart the executor and rollback the step twice
400    int lastStep = 8; // 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      ColumnFamilyDescriptor columnFamilyDescriptor;
416      boolean exception;
417
418      public ConcurrentAddColumnFamily(TableName tableName,
419        ColumnFamilyDescriptor columnFamilyDescriptor) {
420        this.tableName = tableName;
421        this.columnFamilyDescriptor = columnFamilyDescriptor;
422        this.exception = false;
423      }
424
425      public void run() {
426        try {
427          UTIL.getAdmin().addColumnFamily(tableName, columnFamilyDescriptor);
428        } catch (Exception e) {
429          if (e.getClass().equals(ConcurrentTableModificationException.class)) {
430            this.exception = true;
431          }
432        }
433      }
434    }
435    ColumnFamilyDescriptor columnFamilyDescriptor =
436      ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(column_Family2)).build();
437    ConcurrentAddColumnFamily t1 = new ConcurrentAddColumnFamily(tableName, columnFamilyDescriptor);
438    columnFamilyDescriptor =
439      ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(column_Family3)).build();
440    ConcurrentAddColumnFamily t2 = new ConcurrentAddColumnFamily(tableName, columnFamilyDescriptor);
441
442    t1.start();
443    t2.start();
444
445    t1.join();
446    t2.join();
447    int noOfColumnFamilies = UTIL.getAdmin().getDescriptor(tableName).getColumnFamilies().length;
448    assertTrue("Expected ConcurrentTableModificationException.",
449      ((t1.exception || t2.exception) && noOfColumnFamilies == 2) || noOfColumnFamilies == 3);
450  }
451
452  @Test
453  public void testConcurrentDeleteColumnFamily() throws IOException, InterruptedException {
454    final TableName tableName = TableName.valueOf(name.getMethodName());
455    TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tableName);
456    ColumnFamilyDescriptor columnFamilyDescriptor =
457      ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(column_Family1)).build();
458    tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
459    columnFamilyDescriptor =
460      ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(column_Family2)).build();
461    tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
462    columnFamilyDescriptor =
463      ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(column_Family3)).build();
464    tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
465    UTIL.getAdmin().createTable(tableDescriptorBuilder.build());
466
467    class ConcurrentCreateDeleteTable extends Thread {
468      TableName tableName = null;
469      String columnFamily = null;
470      boolean exception;
471
472      public ConcurrentCreateDeleteTable(TableName tableName, String columnFamily) {
473        this.tableName = tableName;
474        this.columnFamily = columnFamily;
475        this.exception = false;
476      }
477
478      public void run() {
479        try {
480          UTIL.getAdmin().deleteColumnFamily(tableName, columnFamily.getBytes());
481        } catch (Exception e) {
482          if (e.getClass().equals(ConcurrentTableModificationException.class)) {
483            this.exception = true;
484          }
485        }
486      }
487    }
488    ConcurrentCreateDeleteTable t1 = new ConcurrentCreateDeleteTable(tableName, column_Family2);
489    ConcurrentCreateDeleteTable t2 = new ConcurrentCreateDeleteTable(tableName, column_Family3);
490
491    t1.start();
492    t2.start();
493
494    t1.join();
495    t2.join();
496    int noOfColumnFamilies = UTIL.getAdmin().getDescriptor(tableName).getColumnFamilies().length;
497    assertTrue("Expected ConcurrentTableModificationException.",
498      ((t1.exception || t2.exception) && noOfColumnFamilies == 2) || noOfColumnFamilies == 1);
499  }
500
501  @Test
502  public void testConcurrentModifyColumnFamily() throws IOException, InterruptedException {
503    final TableName tableName = TableName.valueOf(name.getMethodName());
504    UTIL.createTable(tableName, column_Family1);
505
506    class ConcurrentModifyColumnFamily extends Thread {
507      TableName tableName = null;
508      ColumnFamilyDescriptor hcd = null;
509      boolean exception;
510
511      public ConcurrentModifyColumnFamily(TableName tableName, ColumnFamilyDescriptor hcd) {
512        this.tableName = tableName;
513        this.hcd = hcd;
514        this.exception = false;
515      }
516
517      public void run() {
518        try {
519          UTIL.getAdmin().modifyColumnFamily(tableName, hcd);
520        } catch (Exception e) {
521          if (e.getClass().equals(ConcurrentTableModificationException.class)) {
522            this.exception = true;
523          }
524        }
525      }
526    }
527    ColumnFamilyDescriptor modColumnFamily1 =
528      ColumnFamilyDescriptorBuilder.newBuilder(column_Family1.getBytes()).setMaxVersions(5).build();
529    ColumnFamilyDescriptor modColumnFamily2 =
530      ColumnFamilyDescriptorBuilder.newBuilder(column_Family1.getBytes()).setMaxVersions(6).build();
531
532    ConcurrentModifyColumnFamily t1 = new ConcurrentModifyColumnFamily(tableName, modColumnFamily1);
533    ConcurrentModifyColumnFamily t2 = new ConcurrentModifyColumnFamily(tableName, modColumnFamily2);
534
535    t1.start();
536    t2.start();
537
538    t1.join();
539    t2.join();
540
541    int maxVersions = UTIL.getAdmin().getDescriptor(tableName)
542      .getColumnFamily(column_Family1.getBytes()).getMaxVersions();
543    assertTrue("Expected ConcurrentTableModificationException.", (t1.exception && maxVersions == 5)
544      || (t2.exception && maxVersions == 6) || !(t1.exception && t2.exception));
545  }
546
547  @Test
548  public void testConcurrentModifyTable() throws IOException, InterruptedException {
549    final TableName tableName = TableName.valueOf(name.getMethodName());
550    UTIL.createTable(tableName, column_Family1);
551
552    class ConcurrentModifyTable extends Thread {
553      TableName tableName = null;
554      TableDescriptor htd = null;
555      boolean exception;
556
557      public ConcurrentModifyTable(TableName tableName, TableDescriptor htd) {
558        this.tableName = tableName;
559        this.htd = htd;
560        this.exception = false;
561      }
562
563      public void run() {
564        try {
565          UTIL.getAdmin().modifyTable(htd);
566        } catch (Exception e) {
567          if (e.getClass().equals(ConcurrentTableModificationException.class)) {
568            this.exception = true;
569          }
570        }
571      }
572    }
573    TableDescriptor htd = UTIL.getAdmin().getDescriptor(tableName);
574    TableDescriptor modifiedDescriptor =
575      TableDescriptorBuilder.newBuilder(htd).setCompactionEnabled(false).build();
576
577    ConcurrentModifyTable t1 = new ConcurrentModifyTable(tableName, modifiedDescriptor);
578    ConcurrentModifyTable t2 = new ConcurrentModifyTable(tableName, modifiedDescriptor);
579
580    t1.start();
581    t2.start();
582
583    t1.join();
584    t2.join();
585    assertFalse("Expected ConcurrentTableModificationException.", (t1.exception || t2.exception));
586  }
587}