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 long nonceGroup = HConstants.NO_NONCE;
57    private static long nonce = HConstants.NO_NONCE;
58  
59    private static void setupConf(Configuration conf) {
60      conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
61    }
62  
63    @BeforeClass
64    public static void setupCluster() throws Exception {
65      setupConf(UTIL.getConfiguration());
66      UTIL.startMiniCluster(1);
67    }
68  
69    @AfterClass
70    public static void cleanupTest() throws Exception {
71      try {
72        UTIL.shutdownMiniCluster();
73      } catch (Exception e) {
74        LOG.warn("failure shutting down cluster", e);
75      }
76    }
77  
78    @Before
79    public void setup() throws Exception {
80      resetProcExecutorTestingKillFlag();
81      nonceGroup =
82          MasterProcedureTestingUtility.generateNonceGroup(UTIL.getHBaseCluster().getMaster());
83      nonce = MasterProcedureTestingUtility.generateNonce(UTIL.getHBaseCluster().getMaster());
84    }
85  
86    @After
87    public void tearDown() throws Exception {
88      resetProcExecutorTestingKillFlag();
89      for (HTableDescriptor htd: UTIL.getHBaseAdmin().listTables()) {
90        LOG.info("Tear down, remove table=" + htd.getTableName());
91        UTIL.deleteTable(htd.getTableName());
92      }
93    }
94  
95    private void resetProcExecutorTestingKillFlag() {
96      final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
97      ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
98      assertTrue("expected executor to be running", procExec.isRunning());
99    }
100 
101   @Test(timeout=60000)
102   public void testSimpleCreate() throws Exception {
103     final TableName tableName = TableName.valueOf("testSimpleCreate");
104     final byte[][] splitKeys = null;
105     testSimpleCreate(tableName, splitKeys);
106   }
107 
108   @Test(timeout=60000)
109   public void testSimpleCreateWithSplits() throws Exception {
110     final TableName tableName = TableName.valueOf("testSimpleCreateWithSplits");
111     final byte[][] splitKeys = new byte[][] {
112       Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
113     };
114     testSimpleCreate(tableName, splitKeys);
115   }
116 
117   private void testSimpleCreate(final TableName tableName, byte[][] splitKeys) throws Exception {
118     HRegionInfo[] regions = MasterProcedureTestingUtility.createTable(
119       getMasterProcedureExecutor(), tableName, splitKeys, "f1", "f2");
120     MasterProcedureTestingUtility.validateTableCreation(
121       UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2");
122   }
123 
124   @Test(timeout=60000)
125   public void testCreateWithoutColumnFamily() throws Exception {
126     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
127     final TableName tableName = TableName.valueOf("testCreateWithoutColumnFamily");
128     // create table with 0 families will fail
129     final HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName);
130 
131     // disable sanity check
132     htd.setConfiguration("hbase.table.sanity.checks", Boolean.FALSE.toString());
133     final HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, null);
134 
135     long procId =
136         ProcedureTestingUtility.submitAndWait(procExec,
137             new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
138     final ProcedureInfo result = procExec.getResult(procId);
139     assertEquals(true, result.isFailed());
140     Throwable cause = ProcedureTestingUtility.getExceptionCause(result);
141     assertTrue("expected DoNotRetryIOException, got " + cause,
142         cause instanceof DoNotRetryIOException);
143   }
144 
145   @Test(timeout=60000, expected=TableExistsException.class)
146   public void testCreateExisting() throws Exception {
147     final TableName tableName = TableName.valueOf("testCreateExisting");
148     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
149     final HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f");
150     final HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, null);
151 
152     // create the table
153     long procId1 = procExec.submitProcedure(
154       new CreateTableProcedure(procExec.getEnvironment(), htd, regions), nonceGroup, nonce);
155 
156     // create another with the same name
157     ProcedurePrepareLatch latch2 = new ProcedurePrepareLatch.CompatibilityLatch();
158     long procId2 = procExec.submitProcedure(
159       new CreateTableProcedure(procExec.getEnvironment(), htd, regions, latch2),
160       nonceGroup + 1,
161       nonce + 1);
162 
163     ProcedureTestingUtility.waitProcedure(procExec, procId1);
164     ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId1));
165 
166     ProcedureTestingUtility.waitProcedure(procExec, procId2);
167     latch2.await();
168   }
169 
170   @Test(timeout=60000)
171   public void testCreateTwiceWithSameNonce() throws Exception {
172     final TableName tableName = TableName.valueOf("testCreateTwiceWithSameNonce");
173     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
174     final HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f");
175     final HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, null);
176 
177     // create the table
178     long procId1 = procExec.submitProcedure(
179       new CreateTableProcedure(procExec.getEnvironment(), htd, regions), nonceGroup, nonce);
180 
181     // create another with the same name
182     long procId2 = procExec.submitProcedure(
183       new CreateTableProcedure(procExec.getEnvironment(), htd, regions), nonceGroup, nonce);
184 
185     ProcedureTestingUtility.waitProcedure(procExec, procId1);
186     ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId1));
187 
188     ProcedureTestingUtility.waitProcedure(procExec, procId2);
189     ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId2));
190     assertTrue(procId1 == procId2);
191   }
192 
193   @Test(timeout=60000)
194   public void testRecoveryAndDoubleExecution() throws Exception {
195     final TableName tableName = TableName.valueOf("testRecoveryAndDoubleExecution");
196 
197     // create the table
198     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
199     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
200 
201     // Start the Create procedure && kill the executor
202     byte[][] splitKeys = null;
203     HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f1", "f2");
204     HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, splitKeys);
205     long procId = procExec.submitProcedure(
206       new CreateTableProcedure(procExec.getEnvironment(), htd, regions), nonceGroup, nonce);
207 
208     // Restart the executor and execute the step twice
209     // NOTE: the 6 (number of CreateTableState steps) is hardcoded,
210     //       so you have to look at this test at least once when you add a new step.
211     MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(
212       procExec, procId, 6, CreateTableState.values());
213 
214     MasterProcedureTestingUtility.validateTableCreation(
215       UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2");
216   }
217 
218   @Test(timeout=90000)
219   public void testRollbackAndDoubleExecution() throws Exception {
220     final TableName tableName = TableName.valueOf("testRollbackAndDoubleExecution");
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     htd.setRegionReplication(3);
232     HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, splitKeys);
233     long procId = procExec.submitProcedure(
234       new CreateTableProcedure(procExec.getEnvironment(), htd, regions), nonceGroup, nonce);
235 
236     // NOTE: the 4 (number of CreateTableState steps) is hardcoded,
237     //       so you have to look at this test at least once when you add a new step.
238     MasterProcedureTestingUtility.testRollbackAndDoubleExecution(
239         procExec, procId, 4, CreateTableState.values());
240 
241     MasterProcedureTestingUtility.validateTableDeletion(
242       UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2");
243 
244     // are we able to create the table after a rollback?
245     resetProcExecutorTestingKillFlag();
246     testSimpleCreate(tableName, splitKeys);
247   }
248 
249   @Test(timeout=90000)
250   public void testRollbackRetriableFailure() throws Exception {
251     final TableName tableName = TableName.valueOf("testRollbackRetriableFailure");
252 
253     // create the table
254     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
255     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
256 
257     // Start the Create procedure && kill the executor
258     final byte[][] splitKeys = new byte[][] {
259       Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
260     };
261     HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f1", "f2");
262     HRegionInfo[] regions = ModifyRegionUtils.createHRegionInfos(htd, splitKeys);
263     long procId = procExec.submitProcedure(
264       new FaultyCreateTableProcedure(procExec.getEnvironment(), htd, regions), nonceGroup, nonce);
265 
266     // NOTE: the 4 (number of CreateTableState steps) is hardcoded,
267     //       so you have to look at this test at least once when you add a new step.
268     MasterProcedureTestingUtility.testRollbackRetriableFailure(
269         procExec, procId, 4, CreateTableState.values());
270 
271     MasterProcedureTestingUtility.validateTableDeletion(
272       UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2");
273 
274     // are we able to create the table after a rollback?
275     resetProcExecutorTestingKillFlag();
276     testSimpleCreate(tableName, splitKeys);
277   }
278 
279   private ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() {
280     return UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor();
281   }
282 
283   public static class FaultyCreateTableProcedure extends CreateTableProcedure {
284     private int retries = 0;
285 
286     public FaultyCreateTableProcedure() {
287       // Required by the Procedure framework to create the procedure on replay
288     }
289 
290     public FaultyCreateTableProcedure(final MasterProcedureEnv env,
291         final HTableDescriptor hTableDescriptor, final HRegionInfo[] newRegions)
292         throws IOException {
293       super(env, hTableDescriptor, newRegions);
294     }
295 
296     @Override
297     protected void rollbackState(final MasterProcedureEnv env, final CreateTableState state)
298         throws IOException {
299       if (retries++ < 3) {
300         LOG.info("inject rollback failure state=" + state);
301         throw new IOException("injected failure number " + retries);
302       } else {
303         super.rollbackState(env, state);
304         retries = 0;
305       }
306     }
307   }
308 }