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