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