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