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