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