View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Set;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.atomic.AtomicBoolean;
27  
28  import org.apache.commons.lang.RandomStringUtils;
29  import org.apache.commons.lang.math.RandomUtils;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.client.Admin;
34  import org.apache.hadoop.hbase.client.Connection;
35  import org.apache.hadoop.hbase.client.ConnectionFactory;
36  import org.apache.hadoop.hbase.client.Put;
37  import org.apache.hadoop.hbase.client.Table;
38  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
39  import org.apache.hadoop.hbase.testclassification.IntegrationTests;
40  import org.apache.hadoop.hbase.util.Bytes;
41  import org.apache.hadoop.hbase.util.HBaseFsck;
42  import org.apache.hadoop.hbase.util.Threads;
43  import org.apache.hadoop.hbase.util.hbck.HbckTestingUtil;
44  import org.apache.hadoop.util.ToolRunner;
45  import org.junit.Assert;
46  import org.junit.Test;
47  import org.junit.experimental.categories.Category;
48  
49  /**
50   *
51   * Integration test that verifies Procedure V2. <br/><br/>
52   *
53   * DDL operations should go through (rollforward or rollback) when primary master is killed by
54   * ChaosMonkey (default MASTER_KILLING)<br/><br/>
55   *
56   * Multiple Worker threads are started to randomly do the following Actions in loops:<br/>
57   * Actions generating and populating tables:
58   * <ul>
59   *     <li>CreateTableAction</li>
60   *     <li>DisableTableAction</li>
61   *     <li>EnableTableAction</li>
62   *     <li>DeleteTableAction</li>
63   *     <li>AddRowAction</li>
64   * </ul>
65   * Actions performing column family DDL operations:
66   * <ul>
67   *     <li>AddColumnFamilyAction</li>
68   *     <li>AlterColumnFamilyVersionsAction</li>
69   *     <li>AlterColumnFamilyEncodingAction</li>
70   *     <li>DeleteColumnFamilyAction</li>
71   * </ul>
72   * Actions performing namespace DDL operations:
73   * <ul>
74   *     <li>AddNamespaceAction</li>
75   *     <li>AlterNamespaceAction</li>
76   *     <li>DeleteNamespaceAction</li>
77   * </ul>
78   * <br/>
79   *
80   * The threads run for a period of time (default 20 minutes) then are stopped at the end of
81   * runtime. Verification is performed towards those checkpoints:
82   * <ol>
83   *     <li>No Actions throw Exceptions.</li>
84   *     <li>No inconsistencies are detected in hbck.</li>
85   * </ol>
86   *
87   * <p>
88   * This test should be run by the hbase user since it invokes hbck at the end
89   * </p><p>
90   * Usage:
91   *  hbase org.apache.hadoop.hbase.IntegrationTestDDLMasterFailover
92   *    -Dhbase.IntegrationTestDDLMasterFailover.runtime=1200000
93   *    -Dhbase.IntegrationTestDDLMasterFailover.numThreads=20
94   *    -Dhbase.IntegrationTestDDLMasterFailover.numRegions=50 --monkey masterKilling
95   */
96  
97  @Category(IntegrationTests.class)
98  public class IntegrationTestDDLMasterFailover extends IntegrationTestBase {
99  
100   private static final Log LOG = LogFactory.getLog(IntegrationTestDDLMasterFailover.class);
101 
102   private static final int SERVER_COUNT = 1; // number of slaves for the smallest cluster
103 
104   protected static final long DEFAULT_RUN_TIME = 20 * 60 * 1000;
105 
106   protected static final int DEFAULT_NUM_THREADS = 20;
107 
108   protected static final int DEFAULT_NUM_REGIONS = 50; // number of regions in pre-split tables
109 
110   private boolean keepObjectsAtTheEnd = false;
111   protected HBaseCluster cluster;
112 
113   protected Connection connection;
114 
115   /**
116    * A soft limit on how long we should run
117    */
118   protected static final String RUN_TIME_KEY = "hbase.%s.runtime";
119   protected static final String NUM_THREADS_KEY = "hbase.%s.numThreads";
120   protected static final String NUM_REGIONS_KEY = "hbase.%s.numRegions";
121 
122   protected AtomicBoolean running = new AtomicBoolean(true);
123 
124   protected AtomicBoolean create_table = new AtomicBoolean(true);
125 
126   protected int numThreads, numRegions;
127 
128   ConcurrentHashMap<String, NamespaceDescriptor> namespaceMap =
129       new ConcurrentHashMap<String, NamespaceDescriptor>();
130 
131   ConcurrentHashMap<TableName, HTableDescriptor> enabledTables =
132       new ConcurrentHashMap<TableName, HTableDescriptor>();
133 
134   ConcurrentHashMap<TableName, HTableDescriptor> disabledTables =
135       new ConcurrentHashMap<TableName, HTableDescriptor>();
136 
137   ConcurrentHashMap<TableName, HTableDescriptor> deletedTables =
138       new ConcurrentHashMap<TableName, HTableDescriptor>();
139 
140   @Override
141   public void setUpCluster() throws Exception {
142     util = getTestingUtil(getConf());
143     LOG.debug("Initializing/checking cluster has " + SERVER_COUNT + " servers");
144     util.initializeCluster(getMinServerCount());
145     LOG.debug("Done initializing/checking cluster");
146     cluster = util.getHBaseClusterInterface();
147   }
148 
149   @Override
150   public void cleanUpCluster() throws Exception {
151     if (!keepObjectsAtTheEnd) {
152       Admin admin = util.getHBaseAdmin();
153       admin.disableTables("ittable-\\d+");
154       admin.deleteTables("ittable-\\d+");
155       NamespaceDescriptor [] nsds = admin.listNamespaceDescriptors();
156       for(NamespaceDescriptor nsd: nsds) {
157         if(nsd.getName().matches("itnamespace\\d+")) {
158           LOG.info("Removing namespace="+nsd.getName());
159           admin.deleteNamespace(nsd.getName());
160         }
161       }
162     }
163 
164     enabledTables.clear();
165     disabledTables.clear();
166     deletedTables.clear();
167     namespaceMap.clear();
168 
169     Connection connection = getConnection();
170     connection.close();
171     super.cleanUpCluster();
172   }
173 
174   protected int getMinServerCount() {
175     return SERVER_COUNT;
176   }
177 
178   protected synchronized void setConnection(Connection connection){
179     this.connection = connection;
180   }
181 
182   protected synchronized Connection getConnection(){
183     if (this.connection == null) {
184       try {
185         Connection connection = ConnectionFactory.createConnection(getConf());
186         setConnection(connection);
187       } catch (IOException e) {
188         LOG.fatal("Failed to establish connection.", e);
189       }
190     }
191     return connection;
192   }
193 
194   protected void verifyNamespaces() throws  IOException{
195     Connection connection = getConnection();
196     Admin admin = connection.getAdmin();
197     // iterating concurrent map
198     for (String nsName : namespaceMap.keySet()){
199       try {
200         Assert.assertTrue(
201           "Namespace: " + nsName + " in namespaceMap does not exist",
202           admin.getNamespaceDescriptor(nsName) != null);
203       } catch (NamespaceNotFoundException nsnfe) {
204         Assert.fail(
205           "Namespace: " + nsName + " in namespaceMap does not exist: " + nsnfe.getMessage());
206       }
207     }
208     admin.close();
209   }
210 
211   protected void verifyTables() throws  IOException{
212     Connection connection = getConnection();
213     Admin admin = connection.getAdmin();
214     // iterating concurrent map
215     for (TableName tableName : enabledTables.keySet()){
216       Assert.assertTrue("Table: " + tableName + " in enabledTables is not enabled",
217           admin.isTableEnabled(tableName));
218     }
219     for (TableName tableName : disabledTables.keySet()){
220       Assert.assertTrue("Table: " + tableName + " in disabledTables is not disabled",
221           admin.isTableDisabled(tableName));
222     }
223     for (TableName tableName : deletedTables.keySet()){
224       Assert.assertFalse("Table: " + tableName + " in deletedTables is not deleted",
225           admin.tableExists(tableName));
226     }
227     admin.close();
228   }
229 
230   @Test
231   public void testAsUnitTest() throws Exception {
232     runTest();
233   }
234 
235   @Override
236   public int runTestFromCommandLine() throws Exception {
237     int ret = runTest();
238     return ret;
239   }
240 
241   private abstract class MasterAction{
242     Connection connection = getConnection();
243 
244     abstract void perform() throws IOException;
245   }
246 
247   private abstract class NamespaceAction extends MasterAction {
248     final String nsTestConfigKey = "hbase.namespace.testKey";
249 
250     // NamespaceAction has implemented selectNamespace() shared by multiple namespace Actions
251     protected NamespaceDescriptor selectNamespace(
252         ConcurrentHashMap<String, NamespaceDescriptor> namespaceMap) {
253       // synchronization to prevent removal from multiple threads
254       synchronized (namespaceMap) {
255         // randomly select namespace from namespaceMap
256         if (namespaceMap.isEmpty()) {
257           return null;
258         }
259         ArrayList<String> namespaceList = new ArrayList<String>(namespaceMap.keySet());
260         String randomKey = namespaceList.get(RandomUtils.nextInt(namespaceList.size()));
261         NamespaceDescriptor randomNsd = namespaceMap.get(randomKey);
262         // remove from namespaceMap
263         namespaceMap.remove(randomKey);
264         return randomNsd;
265       }
266     }
267   }
268 
269   private class CreateNamespaceAction extends NamespaceAction {
270     @Override
271     void perform() throws IOException {
272       Admin admin = connection.getAdmin();
273       try {
274         NamespaceDescriptor nsd;
275         while (true) {
276           nsd = createNamespaceDesc();
277           try {
278             if (admin.getNamespaceDescriptor(nsd.getName()) != null) {
279               // the namespace has already existed.
280               continue;
281             } else {
282               // currently, the code never return null - always throws exception if
283               // namespace is not found - this just a defensive programming to make
284               // sure null situation is handled in case the method changes in the
285               // future.
286               break;
287             }
288           } catch (NamespaceNotFoundException nsnfe) {
289             // This is expected for a random generated NamespaceDescriptor
290             break;
291           }
292         }
293         LOG.info("Creating namespace:" + nsd);
294         admin.createNamespace(nsd);
295         NamespaceDescriptor freshNamespaceDesc = admin.getNamespaceDescriptor(nsd.getName());
296         Assert.assertTrue("Namespace: " + nsd + " was not created", freshNamespaceDesc != null);
297         namespaceMap.put(nsd.getName(), freshNamespaceDesc);
298         LOG.info("Created namespace:" + freshNamespaceDesc);
299       } catch (Exception e){
300         LOG.warn("Caught exception in action: " + this.getClass());
301         throw e;
302       } finally {
303         admin.close();
304       }
305     }
306 
307     private NamespaceDescriptor createNamespaceDesc() {
308       String namespaceName = "itnamespace" + String.format("%010d",
309         RandomUtils.nextInt(Integer.MAX_VALUE));
310       NamespaceDescriptor nsd = NamespaceDescriptor.create(namespaceName).build();
311 
312       nsd.setConfiguration(
313         nsTestConfigKey,
314         String.format("%010d", RandomUtils.nextInt(Integer.MAX_VALUE)));
315       return nsd;
316     }
317   }
318 
319   private class ModifyNamespaceAction extends NamespaceAction {
320     @Override
321     void perform() throws IOException {
322       NamespaceDescriptor selected = selectNamespace(namespaceMap);
323       if (selected == null) {
324         return;
325       }
326 
327       Admin admin = connection.getAdmin();
328       try {
329         String namespaceName = selected.getName();
330         LOG.info("Modifying namespace :" + selected);
331         NamespaceDescriptor modifiedNsd = NamespaceDescriptor.create(namespaceName).build();
332         String nsValueNew;
333         do {
334           nsValueNew = String.format("%010d", RandomUtils.nextInt(Integer.MAX_VALUE));
335         } while (selected.getConfigurationValue(nsTestConfigKey).equals(nsValueNew));
336         modifiedNsd.setConfiguration(nsTestConfigKey, nsValueNew);
337         admin.modifyNamespace(modifiedNsd);
338         NamespaceDescriptor freshNamespaceDesc = admin.getNamespaceDescriptor(namespaceName);
339         Assert.assertTrue(
340           "Namespace: " + selected + " was not modified",
341           freshNamespaceDesc.getConfigurationValue(nsTestConfigKey).equals(nsValueNew));
342         Assert.assertTrue(
343           "Namespace: " + namespaceName + " does not exist",
344           admin.getNamespaceDescriptor(namespaceName) != null);
345         namespaceMap.put(namespaceName, freshNamespaceDesc);
346         LOG.info("Modified namespace :" + freshNamespaceDesc);
347       } catch (Exception e){
348         LOG.warn("Caught exception in action: " + this.getClass());
349         throw e;
350       } finally {
351         admin.close();
352       }
353     }
354   }
355 
356   private class DeleteNamespaceAction extends NamespaceAction {
357     @Override
358     void perform() throws IOException {
359       NamespaceDescriptor selected = selectNamespace(namespaceMap);
360       if (selected == null) {
361         return;
362       }
363 
364       Admin admin = connection.getAdmin();
365       try {
366         String namespaceName = selected.getName();
367         LOG.info("Deleting namespace :" + selected);
368         admin.deleteNamespace(namespaceName);
369         try {
370           if (admin.getNamespaceDescriptor(namespaceName) != null) {
371             // the namespace still exists.
372             Assert.assertTrue("Namespace: " + selected + " was not deleted", false);
373           } else {
374             LOG.info("Deleted namespace :" + selected);
375           }
376         } catch (NamespaceNotFoundException nsnfe) {
377           // This is expected result
378           LOG.info("Deleted namespace :" + selected);
379         }
380       } catch (Exception e){
381         LOG.warn("Caught exception in action: " + this.getClass());
382         throw e;
383       } finally {
384         admin.close();
385       }
386     }
387   }
388 
389   private abstract class TableAction extends  MasterAction{
390     // TableAction has implemented selectTable() shared by multiple table Actions
391     protected HTableDescriptor selectTable(ConcurrentHashMap<TableName, HTableDescriptor> tableMap)
392     {
393       // synchronization to prevent removal from multiple threads
394       synchronized (tableMap){
395         // randomly select table from tableMap
396         if (tableMap.isEmpty()) {
397           return null;
398         }
399         ArrayList<TableName> tableList = new ArrayList<TableName>(tableMap.keySet());
400         TableName randomKey = tableList.get(RandomUtils.nextInt(tableList.size()));
401         HTableDescriptor randomHtd = tableMap.get(randomKey);
402         // remove from tableMap
403         tableMap.remove(randomKey);
404         return randomHtd;
405       }
406     }
407   }
408 
409   private class CreateTableAction extends TableAction {
410 
411     @Override
412     void perform() throws IOException {
413       Admin admin = connection.getAdmin();
414       try {
415         HTableDescriptor htd = createTableDesc();
416         TableName tableName = htd.getTableName();
417         if ( admin.tableExists(tableName)){
418           return;
419         }
420         String numRegionKey = String.format(NUM_REGIONS_KEY, this.getClass().getSimpleName());
421         numRegions = getConf().getInt(numRegionKey, DEFAULT_NUM_REGIONS);
422         byte[] startKey = Bytes.toBytes("row-0000000000");
423         byte[] endKey = Bytes.toBytes("row-" + Integer.MAX_VALUE);
424         LOG.info("Creating table:" + htd);
425         admin.createTable(htd, startKey, endKey, numRegions);
426         Assert.assertTrue("Table: " + htd + " was not created", admin.tableExists(tableName));
427         HTableDescriptor freshTableDesc = admin.getTableDescriptor(tableName);
428         Assert.assertTrue(
429           "After create, Table: " + tableName + " in not enabled", admin.isTableEnabled(tableName));
430         enabledTables.put(tableName, freshTableDesc);
431         LOG.info("Created table:" + freshTableDesc);
432       } catch (Exception e) {
433         LOG.warn("Caught exception in action: " + this.getClass());
434         throw e;
435       } finally {
436         admin.close();
437       }
438     }
439 
440     private HTableDescriptor createTableDesc() {
441       String tableName = "ittable-" + String.format("%010d",
442         RandomUtils.nextInt(Integer.MAX_VALUE));
443       String familyName = "cf-" + Math.abs(RandomUtils.nextInt());
444       HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(tableName));
445       // add random column family
446       htd.addFamily(new HColumnDescriptor(familyName));
447       return htd;
448     }
449   }
450 
451   private class DisableTableAction extends TableAction {
452 
453     @Override
454     void perform() throws IOException {
455 
456       HTableDescriptor selected = selectTable(enabledTables);
457       if (selected == null) {
458         return;
459       }
460 
461       Admin admin = connection.getAdmin();
462       try {
463         TableName tableName = selected.getTableName();
464         LOG.info("Disabling table :" + selected);
465         admin.disableTable(tableName);
466         Assert.assertTrue("Table: " + selected + " was not disabled",
467             admin.isTableDisabled(tableName));
468         HTableDescriptor freshTableDesc = admin.getTableDescriptor(tableName);
469         Assert.assertTrue(
470           "After disable, Table: " + tableName + " is not disabled",
471           admin.isTableDisabled(tableName));
472         disabledTables.put(tableName, freshTableDesc);
473         LOG.info("Disabled table :" + freshTableDesc);
474       } catch (Exception e){
475         LOG.warn("Caught exception in action: " + this.getClass());
476         // TODO workaround
477         // loose restriction for TableNotDisabledException/TableNotEnabledException thrown in sync
478         // operations
479         // 1) when enable/disable starts, the table state is changed to ENABLING/DISABLING (ZK node
480         // in 1.x), which will be further changed to ENABLED/DISABLED once the operation completes
481         // 2) if master failover happens in the middle of the enable/disable operation, the new
482         // master will try to recover the tables in ENABLING/DISABLING state, as programmed in
483         // AssignmentManager#recoverTableInEnablingState() and
484         // AssignmentManager#recoverTableInDisablingState()
485         // 3) after the new master initialization completes, the procedure tries to re-do the
486         // enable/disable operation, which was already done. Ignore those exceptions before change
487         // of behaviors of AssignmentManager in presence of PV2
488         if (e instanceof TableNotEnabledException) {
489           LOG.warn("Caught TableNotEnabledException in action: " + this.getClass());
490           e.printStackTrace();
491         } else {
492           throw e;
493         }
494       } finally {
495         admin.close();
496       }
497     }
498   }
499 
500   private class EnableTableAction extends TableAction {
501 
502     @Override
503     void perform() throws IOException {
504 
505       HTableDescriptor selected = selectTable(disabledTables);
506       if (selected == null ) {
507         return;
508       }
509 
510       Admin admin = connection.getAdmin();
511       try {
512         TableName tableName = selected.getTableName();
513         LOG.info("Enabling table :" + selected);
514         admin.enableTable(tableName);
515         Assert.assertTrue("Table: " + selected + " was not enabled",
516             admin.isTableEnabled(tableName));
517         HTableDescriptor freshTableDesc = admin.getTableDescriptor(tableName);
518         Assert.assertTrue(
519           "After enable, Table: " + tableName + " in not enabled", admin.isTableEnabled(tableName));
520         enabledTables.put(tableName, freshTableDesc);
521         LOG.info("Enabled table :" + freshTableDesc);
522       } catch (Exception e){
523         LOG.warn("Caught exception in action: " + this.getClass());
524         // TODO workaround
525         // loose restriction for TableNotDisabledException/TableNotEnabledException thrown in sync
526         // operations 1) when enable/disable starts, the table state is changed to
527         // ENABLING/DISABLING (ZK node in 1.x), which will be further changed to ENABLED/DISABLED
528         // once the operation completes 2) if master failover happens in the middle of the
529         // enable/disable operation, the new master will try to recover the tables in
530         // ENABLING/DISABLING state, as programmed in
531         // AssignmentManager#recoverTableInEnablingState() and
532         // AssignmentManager#recoverTableInDisablingState()
533         // 3) after the new master initialization completes, the procedure tries to re-do the
534         // enable/disable operation, which was already done. Ignore those exceptions before
535         // change of behaviors of AssignmentManager in presence of PV2
536         if (e instanceof TableNotDisabledException) {
537           LOG.warn("Caught TableNotDisabledException in action: " + this.getClass());
538           e.printStackTrace();
539         } else {
540           throw e;
541         }
542       } finally {
543         admin.close();
544       }
545     }
546   }
547 
548   private class DeleteTableAction extends TableAction {
549 
550     @Override
551     void perform() throws IOException {
552 
553       HTableDescriptor selected = selectTable(disabledTables);
554       if (selected == null) {
555         return;
556       }
557 
558       Admin admin = connection.getAdmin();
559       try {
560         TableName tableName = selected.getTableName();
561         LOG.info("Deleting table :" + selected);
562         admin.deleteTable(tableName);
563         Assert.assertFalse("Table: " + selected + " was not deleted",
564                 admin.tableExists(tableName));
565         deletedTables.put(tableName, selected);
566         LOG.info("Deleted table :" + selected);
567       } catch (Exception e) {
568         LOG.warn("Caught exception in action: " + this.getClass());
569         throw e;
570       } finally {
571         admin.close();
572       }
573     }
574   }
575 
576 
577   private abstract class ColumnAction extends TableAction{
578     // ColumnAction has implemented selectFamily() shared by multiple family Actions
579     protected HColumnDescriptor selectFamily(HTableDescriptor htd) {
580       if (htd == null) {
581         return null;
582       }
583       HColumnDescriptor[] families = htd.getColumnFamilies();
584       if (families.length == 0){
585         LOG.info("No column families in table: " + htd);
586         return null;
587       }
588       HColumnDescriptor randomCfd = families[RandomUtils.nextInt(families.length)];
589       return randomCfd;
590     }
591   }
592 
593   private class AddColumnFamilyAction extends ColumnAction {
594 
595     @Override
596     void perform() throws IOException {
597       HTableDescriptor selected = selectTable(disabledTables);
598       if (selected == null) {
599         return;
600       }
601 
602       Admin admin = connection.getAdmin();
603       try {
604         HColumnDescriptor cfd = createFamilyDesc();
605         if (selected.hasFamily(cfd.getName())){
606           LOG.info(new String(cfd.getName()) + " already exists in table "
607               + selected.getTableName());
608           return;
609         }
610         TableName tableName = selected.getTableName();
611         LOG.info("Adding column family: " + cfd + " to table: " + tableName);
612         admin.addColumn(tableName, cfd);
613         // assertion
614         HTableDescriptor freshTableDesc = admin.getTableDescriptor(tableName);
615         Assert.assertTrue("Column family: " + cfd + " was not added",
616             freshTableDesc.hasFamily(cfd.getName()));
617         Assert.assertTrue(
618           "After add column family, Table: " + tableName + " is not disabled",
619           admin.isTableDisabled(tableName));
620         disabledTables.put(tableName, freshTableDesc);
621         LOG.info("Added column family: " + cfd + " to table: " + tableName);
622       } catch (Exception e) {
623         LOG.warn("Caught exception in action: " + this.getClass());
624         throw e;
625       } finally {
626         admin.close();
627       }
628     }
629 
630     private HColumnDescriptor createFamilyDesc() {
631       String familyName = "cf-" + String.format("%010d", RandomUtils.nextInt(Integer.MAX_VALUE));
632       HColumnDescriptor cfd = new HColumnDescriptor(familyName);
633       return cfd;
634     }
635   }
636 
637   private class AlterFamilyVersionsAction extends ColumnAction {
638 
639     @Override
640     void perform() throws IOException {
641       HTableDescriptor selected = selectTable(disabledTables);
642       if (selected == null) {
643         return;
644       }
645       HColumnDescriptor columnDesc = selectFamily(selected);
646       if (columnDesc == null){
647         return;
648       }
649 
650       Admin admin = connection.getAdmin();
651       int versions = RandomUtils.nextInt(10) + 3;
652       try {
653         TableName tableName = selected.getTableName();
654         LOG.info("Altering versions of column family: " + columnDesc + " to: " + versions +
655             " in table: " + tableName);
656         columnDesc.setMinVersions(versions);
657         columnDesc.setMaxVersions(versions);
658         admin.modifyTable(tableName, selected);
659         // assertion
660         HTableDescriptor freshTableDesc = admin.getTableDescriptor(tableName);
661         HColumnDescriptor freshColumnDesc = freshTableDesc.getFamily(columnDesc.getName());
662         Assert.assertEquals("Column family: " + columnDesc + " was not altered",
663             freshColumnDesc.getMaxVersions(), versions);
664         Assert.assertEquals("Column family: " + freshColumnDesc + " was not altered",
665             freshColumnDesc.getMinVersions(), versions);
666         Assert.assertTrue(
667           "After alter versions of column family, Table: " + tableName + " is not disabled",
668           admin.isTableDisabled(tableName));
669         disabledTables.put(tableName, freshTableDesc);
670         LOG.info("Altered versions of column family: " + columnDesc + " to: " + versions +
671           " in table: " + tableName);
672       } catch (Exception e) {
673         LOG.warn("Caught exception in action: " + this.getClass());
674         throw e;
675       } finally {
676         admin.close();
677       }
678     }
679   }
680 
681   private class AlterFamilyEncodingAction extends ColumnAction {
682 
683     @Override
684     void perform() throws IOException {
685       HTableDescriptor selected = selectTable(disabledTables);
686       if (selected == null) {
687         return;
688       }
689       HColumnDescriptor columnDesc = selectFamily(selected);
690       if (columnDesc == null){
691         return;
692       }
693 
694       Admin admin = connection.getAdmin();
695       try {
696         TableName tableName = selected.getTableName();
697         // possible DataBlockEncoding ids
698         int[] possibleIds = {0, 2, 3, 4, 6};
699         short id = (short) possibleIds[RandomUtils.nextInt(possibleIds.length)];
700         LOG.info("Altering encoding of column family: " + columnDesc + " to: " + id +
701             " in table: " + tableName);
702         columnDesc.setDataBlockEncoding(DataBlockEncoding.getEncodingById(id));
703         admin.modifyTable(tableName, selected);
704         // assertion
705         HTableDescriptor freshTableDesc = admin.getTableDescriptor(tableName);
706         HColumnDescriptor freshColumnDesc = freshTableDesc.getFamily(columnDesc.getName());
707         Assert.assertEquals("Encoding of column family: " + columnDesc + " was not altered",
708             freshColumnDesc.getDataBlockEncoding().getId(), id);
709         Assert.assertTrue(
710           "After alter encoding of column family, Table: " + tableName + " is not disabled",
711           admin.isTableDisabled(tableName));
712         disabledTables.put(tableName, freshTableDesc);
713         LOG.info("Altered encoding of column family: " + freshColumnDesc + " to: " + id +
714           " in table: " + tableName);
715       } catch (Exception e) {
716         LOG.warn("Caught exception in action: " + this.getClass());
717         throw e;
718       } finally {
719         admin.close();
720       }
721     }
722   }
723 
724   private class DeleteColumnFamilyAction extends ColumnAction {
725 
726     @Override
727     void perform() throws IOException {
728       HTableDescriptor selected = selectTable(disabledTables);
729       HColumnDescriptor cfd = selectFamily(selected);
730       if (selected == null || cfd == null) {
731         return;
732       }
733 
734       Admin admin = connection.getAdmin();
735       try {
736         if (selected.getColumnFamilies().length < 2) {
737           LOG.info("No enough column families to delete in table " + selected.getTableName());
738           return;
739         }
740         TableName tableName = selected.getTableName();
741         LOG.info("Deleting column family: " + cfd + " from table: " + tableName);
742         admin.deleteColumn(tableName, cfd.getName());
743         // assertion
744         HTableDescriptor freshTableDesc = admin.getTableDescriptor(tableName);
745         Assert.assertFalse("Column family: " + cfd + " was not added",
746             freshTableDesc.hasFamily(cfd.getName()));
747         Assert.assertTrue(
748           "After delete column family, Table: " + tableName + " is not disabled",
749           admin.isTableDisabled(tableName));
750         disabledTables.put(tableName, freshTableDesc);
751         LOG.info("Deleted column family: " + cfd + " from table: " + tableName);
752       } catch (Exception e) {
753         LOG.warn("Caught exception in action: " + this.getClass());
754         throw e;
755       } finally {
756         admin.close();
757       }
758     }
759   }
760 
761   private class AddRowAction extends ColumnAction {
762     // populate tables
763     @Override
764     void perform() throws IOException {
765       HTableDescriptor selected = selectTable(enabledTables);
766       if (selected == null ) {
767         return;
768       }
769 
770       Admin admin = connection.getAdmin();
771       TableName tableName = selected.getTableName();
772       try (Table table = connection.getTable(tableName)){
773         ArrayList<HRegionInfo> regionInfos = new ArrayList<HRegionInfo>(admin.getTableRegions(
774             selected.getTableName()));
775         int numRegions = regionInfos.size();
776         // average number of rows to be added per action to each region
777         int average_rows = 1;
778         int numRows = average_rows * numRegions;
779         LOG.info("Adding " + numRows + " rows to table: " + selected);
780         for (int i = 0; i < numRows; i++){
781           // nextInt(Integer.MAX_VALUE)) to return positive numbers only
782           byte[] rowKey = Bytes.toBytes(
783               "row-" + String.format("%010d", RandomUtils.nextInt(Integer.MAX_VALUE)));
784           HColumnDescriptor cfd = selectFamily(selected);
785           if (cfd == null){
786             return;
787           }
788           byte[] family = cfd.getName();
789           byte[] qualifier = Bytes.toBytes("col-" + RandomUtils.nextInt(Integer.MAX_VALUE) % 10);
790           byte[] value = Bytes.toBytes("val-" + RandomStringUtils.randomAlphanumeric(10));
791           Put put = new Put(rowKey);
792           put.addColumn(family, qualifier, value);
793           table.put(put);
794         }
795         HTableDescriptor freshTableDesc = admin.getTableDescriptor(tableName);
796         Assert.assertTrue(
797           "After insert, Table: " + tableName + " in not enabled", admin.isTableEnabled(tableName));
798         enabledTables.put(tableName, freshTableDesc);
799         LOG.info("Added " + numRows + " rows to table: " + selected);
800       } catch (Exception e) {
801         LOG.warn("Caught exception in action: " + this.getClass());
802         throw e;
803       } finally {
804         admin.close();
805       }
806     }
807   }
808 
809   private enum ACTION {
810     CREATE_NAMESPACE,
811     MODIFY_NAMESPACE,
812     DELETE_NAMESPACE,
813     CREATE_TABLE,
814     DISABLE_TABLE,
815     ENABLE_TABLE,
816     DELETE_TABLE,
817     ADD_COLUMNFAMILY,
818     DELETE_COLUMNFAMILY,
819     ALTER_FAMILYVERSIONS,
820     ALTER_FAMILYENCODING,
821     ADD_ROW
822   }
823 
824   private class Worker extends Thread {
825 
826     private Exception savedException;
827 
828     private ACTION action;
829 
830     @Override
831     public void run() {
832       while (running.get()) {
833         // select random action
834         ACTION selectedAction = ACTION.values()[RandomUtils.nextInt() % ACTION.values().length];
835         this.action = selectedAction;
836         LOG.info("Performing Action: " + selectedAction);
837 
838         try {
839           switch (selectedAction) {
840           case CREATE_NAMESPACE:
841             new CreateNamespaceAction().perform();
842             break;
843           case MODIFY_NAMESPACE:
844             new ModifyNamespaceAction().perform();
845             break;
846           case DELETE_NAMESPACE:
847             new DeleteNamespaceAction().perform();
848             break;
849           case CREATE_TABLE:
850             // stop creating new tables in the later stage of the test to avoid too many empty
851             // tables
852             if (create_table.get()) {
853               new CreateTableAction().perform();
854             }
855             break;
856           case ADD_ROW:
857             new AddRowAction().perform();
858             break;
859           case DISABLE_TABLE:
860             new DisableTableAction().perform();
861             break;
862           case ENABLE_TABLE:
863             new EnableTableAction().perform();
864             break;
865           case DELETE_TABLE:
866             // reduce probability of deleting table to 20%
867             if (RandomUtils.nextInt(100) < 20) {
868               new DeleteTableAction().perform();
869             }
870             break;
871           case ADD_COLUMNFAMILY:
872             new AddColumnFamilyAction().perform();
873             break;
874           case DELETE_COLUMNFAMILY:
875             // reduce probability of deleting column family to 20%
876             if (RandomUtils.nextInt(100) < 20) {
877               new DeleteColumnFamilyAction().perform();
878             }
879             break;
880           case ALTER_FAMILYVERSIONS:
881             new AlterFamilyVersionsAction().perform();
882             break;
883           case ALTER_FAMILYENCODING:
884             new AlterFamilyEncodingAction().perform();
885             break;
886           }
887         } catch (Exception ex) {
888           this.savedException = ex;
889           return;
890         }
891       }
892       LOG.info(this.getName() + " stopped");
893     }
894 
895     public Exception getSavedException(){
896       return this.savedException;
897     }
898 
899     public ACTION getAction(){
900       return this.action;
901     }
902   }
903 
904   private void checkException(List<Worker> workers){
905     if(workers == null || workers.isEmpty())
906       return;
907     for (Worker worker : workers){
908       Exception e = worker.getSavedException();
909       if (e != null) {
910         LOG.error("Found exception in thread: " + worker.getName());
911         e.printStackTrace();
912       }
913       Assert.assertNull("Action failed: " + worker.getAction() + " in thread: "
914           + worker.getName(), e);
915     }
916   }
917 
918   private int runTest() throws Exception {
919     LOG.info("Starting the test");
920 
921     String runtimeKey = String.format(RUN_TIME_KEY, this.getClass().getSimpleName());
922     long runtime = util.getConfiguration().getLong(runtimeKey, DEFAULT_RUN_TIME);
923 
924     String numThreadKey = String.format(NUM_THREADS_KEY, this.getClass().getSimpleName());
925     numThreads = util.getConfiguration().getInt(numThreadKey, DEFAULT_NUM_THREADS);
926 
927     ArrayList<Worker> workers = new ArrayList<>();
928     for (int i = 0; i < numThreads; i++) {
929       checkException(workers);
930       Worker worker = new Worker();
931       LOG.info("Launching worker thread " + worker.getName());
932       workers.add(worker);
933       worker.start();
934     }
935 
936     Threads.sleep(runtime / 2);
937     LOG.info("Stopping creating new tables");
938     create_table.set(false);
939     Threads.sleep(runtime / 2);
940     LOG.info("Runtime is up");
941     running.set(false);
942 
943     checkException(workers);
944 
945     for (Worker worker : workers) {
946       worker.join();
947     }
948     LOG.info("All Worker threads stopped");
949 
950     // verify
951     LOG.info("Verify actions of all threads succeeded");
952     checkException(workers);
953     LOG.info("Verify namespaces");
954     verifyNamespaces();
955     LOG.info("Verify states of all tables");
956     verifyTables();
957 
958     // RUN HBCK
959 
960     HBaseFsck hbck = null;
961     try {
962       LOG.info("Running hbck");
963       hbck = HbckTestingUtil.doFsck(util.getConfiguration(), false);
964       if (HbckTestingUtil.inconsistencyFound(hbck)) {
965         // Find the inconsistency during HBCK. Leave table and namespace undropped so that
966         // we can check outside the test.
967         keepObjectsAtTheEnd = true;
968       }
969       HbckTestingUtil.assertNoErrors(hbck);
970       LOG.info("Finished hbck");
971     } finally {
972       if (hbck != null) {
973         hbck.close();
974       }
975     }
976      return 0;
977   }
978 
979   @Override
980   public TableName getTablename() {
981     return null; // This test is not inteded to run with stock Chaos Monkey
982   }
983 
984   @Override
985   protected Set<String> getColumnFamilies() {
986     return null; // This test is not inteded to run with stock Chaos Monkey
987   }
988 
989   public static void main(String[] args) throws Exception {
990     Configuration conf = HBaseConfiguration.create();
991     IntegrationTestingUtility.setUseDistributedCluster(conf);
992     IntegrationTestDDLMasterFailover masterFailover = new IntegrationTestDDLMasterFailover();
993     Connection connection = null;
994     int ret = 1;
995     try {
996       // Initialize connection once, then pass to Actions
997       LOG.debug("Setting up connection ...");
998       connection = ConnectionFactory.createConnection(conf);
999       masterFailover.setConnection(connection);
1000       ret = ToolRunner.run(conf, masterFailover, args);
1001     } catch (IOException e){
1002       LOG.fatal("Failed to establish connection. Aborting test ...", e);
1003     } finally {
1004       connection = masterFailover.getConnection();
1005       if (connection != null){
1006         connection.close();
1007       }
1008       System.exit(ret);
1009     }
1010   }
1011 }