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.master.procedure;
20  
21  import java.io.IOException;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.hadoop.conf.Configuration;
26  import org.apache.hadoop.hbase.DoNotRetryIOException;
27  import org.apache.hadoop.hbase.HBaseTestingUtility;
28  import org.apache.hadoop.hbase.HConstants;
29  import org.apache.hadoop.hbase.HRegionInfo;
30  import org.apache.hadoop.hbase.HTableDescriptor;
31  import org.apache.hadoop.hbase.ProcedureInfo;
32  import org.apache.hadoop.hbase.TableExistsException;
33  import org.apache.hadoop.hbase.TableName;
34  import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
35  import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
36  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.CreateTableState;
37  import org.apache.hadoop.hbase.testclassification.MediumTests;
38  import org.apache.hadoop.hbase.util.Bytes;
39  import org.apache.hadoop.hbase.util.ModifyRegionUtils;
40  import org.junit.After;
41  import org.junit.AfterClass;
42  import org.junit.Before;
43  import org.junit.BeforeClass;
44  import org.junit.Test;
45  import org.junit.experimental.categories.Category;
46  
47  import static org.junit.Assert.assertEquals;
48  import static org.junit.Assert.assertTrue;
49  
50  @Category(MediumTests.class)
51  public class TestCreateTableProcedure {
52    private static final Log LOG = LogFactory.getLog(TestCreateTableProcedure.class);
53  
54    protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
55  
56    private static void setupConf(Configuration conf) {
57      conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
58    }
59  
60    @BeforeClass
61    public static void setupCluster() throws Exception {
62      setupConf(UTIL.getConfiguration());
63      UTIL.startMiniCluster(1);
64    }
65  
66    @AfterClass
67    public static void cleanupTest() throws Exception {
68      try {
69        UTIL.shutdownMiniCluster();
70      } catch (Exception e) {
71        LOG.warn("failure shutting down cluster", e);
72      }
73    }
74  
75    @Before
76    public void setup() throws Exception {
77      resetProcExecutorTestingKillFlag();
78    }
79  
80    @After
81    public void tearDown() throws Exception {
82      resetProcExecutorTestingKillFlag();
83      for (HTableDescriptor htd: UTIL.getHBaseAdmin().listTables()) {
84        LOG.info("Tear down, remove table=" + htd.getTableName());
85        UTIL.deleteTable(htd.getTableName());
86      }
87    }
88  
89    private void resetProcExecutorTestingKillFlag() {
90      final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
91      ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
92      assertTrue("expected executor to be running", procExec.isRunning());
93    }
94  
95    @Test(timeout=60000)
96    public void testSimpleCreate() throws Exception {
97      final TableName tableName = TableName.valueOf("testSimpleCreate");
98      final byte[][] splitKeys = null;
99      testSimpleCreate(tableName, splitKeys);
100   }
101 
102   @Test(timeout=60000)
103   public void testSimpleCreateWithSplits() throws Exception {
104     final TableName tableName = TableName.valueOf("testSimpleCreateWithSplits");
105     final byte[][] splitKeys = new byte[][] {
106       Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
107     };
108     testSimpleCreate(tableName, splitKeys);
109   }
110 
111   private void testSimpleCreate(final TableName tableName, byte[][] splitKeys) throws Exception {
112     HRegionInfo[] regions = MasterProcedureTestingUtility.createTable(
113       getMasterProcedureExecutor(), tableName, splitKeys, "f1", "f2");
114     MasterProcedureTestingUtility.validateTableCreation(
115       UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2");
116   }
117 
118   @Test(timeout=60000)
119   public void testCreateWithoutColumnFamily() throws Exception {
120     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
121     final TableName tableName = TableName.valueOf("testCreateWithoutColumnFamily");
122     // create table with 0 families will fail
123     final HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName);
124 
125     // disable sanity check
126     htd.setConfiguration("hbase.table.sanity.checks", Boolean.FALSE.toString());
127     final HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, null);
128 
129     long procId =
130         ProcedureTestingUtility.submitAndWait(procExec,
131             new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
132     final ProcedureInfo result = procExec.getResult(procId);
133     assertEquals(true, result.isFailed());
134     Throwable cause = ProcedureTestingUtility.getExceptionCause(result);
135     assertTrue("expected DoNotRetryIOException, got " + cause,
136         cause instanceof DoNotRetryIOException);
137   }
138 
139   @Test(timeout=60000, expected=TableExistsException.class)
140   public void testCreateExisting() throws Exception {
141     final TableName tableName = TableName.valueOf("testCreateExisting");
142     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
143     final HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f");
144     final HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, null);
145 
146     // create the table
147     long procId1 = procExec.submitProcedure(
148       new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
149 
150     // create another with the same name
151     ProcedurePrepareLatch latch2 = new ProcedurePrepareLatch.CompatibilityLatch();
152     long procId2 = procExec.submitProcedure(
153       new CreateTableProcedure(procExec.getEnvironment(), htd, regions, latch2));
154 
155     ProcedureTestingUtility.waitProcedure(procExec, procId1);
156     ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId1));
157 
158     ProcedureTestingUtility.waitProcedure(procExec, procId2);
159     latch2.await();
160   }
161 
162   @Test(timeout=60000)
163   public void testRecoveryAndDoubleExecution() throws Exception {
164     final TableName tableName = TableName.valueOf("testRecoveryAndDoubleExecution");
165 
166     // create the table
167     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
168     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
169 
170     // Start the Create procedure && kill the executor
171     byte[][] splitKeys = null;
172     HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f1", "f2");
173     HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, splitKeys);
174     long procId = procExec.submitProcedure(
175       new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
176 
177     // Restart the executor and execute the step twice
178     // NOTE: the 6 (number of CreateTableState steps) is hardcoded,
179     //       so you have to look at this test at least once when you add a new step.
180     MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(
181       procExec, procId, 6, CreateTableState.values());
182 
183     MasterProcedureTestingUtility.validateTableCreation(
184       UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2");
185   }
186 
187   @Test(timeout=90000)
188   public void testRollbackAndDoubleExecution() throws Exception {
189     final TableName tableName = TableName.valueOf("testRollbackAndDoubleExecution");
190 
191     // create the table
192     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
193     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
194 
195     // Start the Create procedure && kill the executor
196     final byte[][] splitKeys = new byte[][] {
197       Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
198     };
199     HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f1", "f2");
200     htd.setRegionReplication(3);
201     HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, splitKeys);
202     long procId = procExec.submitProcedure(
203       new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
204 
205     // NOTE: the 4 (number of CreateTableState steps) is hardcoded,
206     //       so you have to look at this test at least once when you add a new step.
207     MasterProcedureTestingUtility.testRollbackAndDoubleExecution(
208         procExec, procId, 4, CreateTableState.values());
209 
210     MasterProcedureTestingUtility.validateTableDeletion(
211       UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2");
212 
213     // are we able to create the table after a rollback?
214     resetProcExecutorTestingKillFlag();
215     testSimpleCreate(tableName, splitKeys);
216   }
217 
218   @Test(timeout=90000)
219   public void testRollbackRetriableFailure() throws Exception {
220     final TableName tableName = TableName.valueOf("testRollbackRetriableFailure");
221 
222     // create the table
223     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
224     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
225 
226     // Start the Create procedure && kill the executor
227     final byte[][] splitKeys = new byte[][] {
228       Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
229     };
230     HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f1", "f2");
231     HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, splitKeys);
232     long procId = procExec.submitProcedure(
233       new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
234 
235     // NOTE: the 4 (number of CreateTableState steps) is hardcoded,
236     //       so you have to look at this test at least once when you add a new step.
237     MasterProcedureTestingUtility.testRollbackRetriableFailure(
238         procExec, procId, 4, CreateTableState.values());
239 
240     MasterProcedureTestingUtility.validateTableDeletion(
241       UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2");
242 
243     // are we able to create the table after a rollback?
244     resetProcExecutorTestingKillFlag();
245     testSimpleCreate(tableName, splitKeys);
246   }
247 
248   private ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() {
249     return UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor();
250   }
251 
252   public static class FaultyCreateTableProcedure extends CreateTableProcedure {
253     private int retries = 0;
254 
255     public FaultyCreateTableProcedure() {
256       // Required by the Procedure framework to create the procedure on replay
257     }
258 
259     public FaultyCreateTableProcedure(final MasterProcedureEnv env,
260         final HTableDescriptor hTableDescriptor, final HRegionInfo[] newRegions)
261         throws IOException {
262       super(env, hTableDescriptor, newRegions);
263     }
264 
265     @Override
266     protected void rollbackState(final MasterProcedureEnv env, final CreateTableState state)
267         throws IOException {
268       if (retries++ < 3) {
269         LOG.info("inject rollback failure state=" + state);
270         throw new IOException("injected failure number " + retries);
271       } else {
272         super.rollbackState(env, state);
273         retries = 0;
274       }
275     }
276   }
277 }