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 static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertTrue;
23  import static org.junit.Assert.fail;
24  
25  import java.io.IOException;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.conf.Configuration;
29  import org.apache.hadoop.fs.FileSystem;
30  import org.apache.hadoop.fs.Path;
31  import org.apache.hadoop.hbase.HBaseIOException;
32  import org.apache.hadoop.hbase.HBaseTestingUtility;
33  import org.apache.hadoop.hbase.HRegionInfo;
34  import org.apache.hadoop.hbase.HTableDescriptor;
35  import org.apache.hadoop.hbase.ProcedureInfo;
36  import org.apache.hadoop.hbase.TableName;
37  import org.apache.hadoop.hbase.TableNotDisabledException;
38  import org.apache.hadoop.hbase.TableNotFoundException;
39  import org.apache.hadoop.hbase.master.MasterFileSystem;
40  import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
41  import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
42  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
43  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.TruncateTableState;
44  import org.apache.hadoop.hbase.testclassification.MediumTests;
45  import org.apache.hadoop.hbase.util.Bytes;
46  import org.apache.hadoop.hbase.util.FSUtils;
47  import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
48  import org.junit.After;
49  import org.junit.AfterClass;
50  import org.junit.Before;
51  import org.junit.BeforeClass;
52  import org.junit.Test;
53  import org.junit.experimental.categories.Category;
54  
55  @Category(MediumTests.class)
56  public class TestTruncateTableProcedure {
57    private static final Log LOG = LogFactory.getLog(TestTruncateTableProcedure.class);
58  
59    protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
60  
61    private static void setupConf(Configuration conf) {
62      conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
63    }
64  
65    @BeforeClass
66    public static void setupCluster() throws Exception {
67      setupConf(UTIL.getConfiguration());
68      UTIL.startMiniCluster(1);
69    }
70  
71    @AfterClass
72    public static void cleanupTest() throws Exception {
73      try {
74        UTIL.shutdownMiniCluster();
75      } catch (Exception e) {
76        LOG.warn("failure shutting down cluster", e);
77      }
78    }
79  
80    @Before
81    public void setup() throws Exception {
82      final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
83      ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
84      assertTrue("expected executor to be running", procExec.isRunning());
85    }
86  
87    @After
88    public void tearDown() throws Exception {
89      ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
90      for (HTableDescriptor htd: UTIL.getHBaseAdmin().listTables()) {
91        LOG.info("Tear down, remove table=" + htd.getTableName());
92        UTIL.deleteTable(htd.getTableName());
93      }
94    }
95  
96    @Test(timeout=60000)
97    public void testTruncateNotExistentTable() throws Exception {
98      final TableName tableName = TableName.valueOf("testTruncateNotExistentTable");
99  
100     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
101     long procId = ProcedureTestingUtility.submitAndWait(procExec,
102         new TruncateTableProcedure(procExec.getEnvironment(), tableName, true));
103 
104     // Second delete should fail with TableNotFound
105     ProcedureInfo result = procExec.getResult(procId);
106     assertTrue(result.isFailed());
107     LOG.debug("Truncate failed with exception: " + result.getExceptionFullMessage());
108     assertTrue(ProcedureTestingUtility.getExceptionCause(result) instanceof TableNotFoundException);
109   }
110 
111   @Test(timeout=60000)
112   public void testTruncateNotDisabledTable() throws Exception {
113     final TableName tableName = TableName.valueOf("testTruncateNotDisabledTable");
114 
115     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
116     MasterProcedureTestingUtility.createTable(procExec, tableName, null, "f");
117 
118     long procId = ProcedureTestingUtility.submitAndWait(procExec,
119         new TruncateTableProcedure(procExec.getEnvironment(), tableName, false));
120 
121     // Second delete should fail with TableNotDisabled
122     ProcedureInfo result = procExec.getResult(procId);
123     assertTrue(result.isFailed());
124     LOG.debug("Truncate failed with exception: " + result.getExceptionFullMessage());
125     assertTrue(
126       ProcedureTestingUtility.getExceptionCause(result) instanceof TableNotDisabledException);
127   }
128 
129   @Test(timeout=60000)
130   public void testSimpleTruncatePreserveSplits() throws Exception {
131     final TableName tableName = TableName.valueOf("testSimpleTruncatePreserveSplits");
132     testSimpleTruncate(tableName, true);
133   }
134 
135   @Test(timeout=60000)
136   public void testSimpleTruncateNoPreserveSplits() throws Exception {
137     final TableName tableName = TableName.valueOf("testSimpleTruncateNoPreserveSplits");
138     testSimpleTruncate(tableName, false);
139   }
140 
141   private void testSimpleTruncate(final TableName tableName, final boolean preserveSplits)
142       throws Exception {
143     final String[] families = new String[] { "f1", "f2" };
144     final byte[][] splitKeys = new byte[][] {
145       Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
146     };
147 
148     HRegionInfo[] regions = MasterProcedureTestingUtility.createTable(
149       getMasterProcedureExecutor(), tableName, splitKeys, families);
150     // load and verify that there are rows in the table
151     MasterProcedureTestingUtility.loadData(
152       UTIL.getConnection(), tableName, 100, splitKeys, families);
153     assertEquals(100, UTIL.countRows(tableName));
154     // disable the table
155     UTIL.getHBaseAdmin().disableTable(tableName);
156 
157     // truncate the table
158     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
159     long procId = ProcedureTestingUtility.submitAndWait(procExec,
160       new TruncateTableProcedure(procExec.getEnvironment(), tableName, preserveSplits));
161     ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
162 
163     UTIL.waitUntilAllRegionsAssigned(tableName);
164 
165     // validate the table regions and layout
166     regions = UTIL.getHBaseAdmin().getTableRegions(tableName).toArray(new HRegionInfo[0]);
167     if (preserveSplits) {
168       assertEquals(1 + splitKeys.length, regions.length);
169     } else {
170       assertEquals(1, regions.length);
171     }
172     MasterProcedureTestingUtility.validateTableCreation(
173       UTIL.getHBaseCluster().getMaster(), tableName, regions, families);
174 
175     // verify that there are no rows in the table
176     assertEquals(0, UTIL.countRows(tableName));
177 
178     // verify that the table is read/writable
179     MasterProcedureTestingUtility.loadData(
180       UTIL.getConnection(), tableName, 50, splitKeys, families);
181     assertEquals(50, UTIL.countRows(tableName));
182   }
183 
184   @Test(timeout=60000)
185   public void testRecoveryAndDoubleExecutionPreserveSplits() throws Exception {
186     final TableName tableName = TableName.valueOf("testRecoveryAndDoubleExecutionPreserveSplits");
187     testRecoveryAndDoubleExecution(tableName, true);
188   }
189 
190   @Test(timeout=60000)
191   public void testRecoveryAndDoubleExecutionNoPreserveSplits() throws Exception {
192     final TableName tableName = TableName.valueOf("testRecoveryAndDoubleExecutionNoPreserveSplits");
193     testRecoveryAndDoubleExecution(tableName, false);
194   }
195 
196   private void testRecoveryAndDoubleExecution(final TableName tableName,
197       final boolean preserveSplits) throws Exception {
198     final String[] families = new String[] { "f1", "f2" };
199 
200     // create the table
201     final byte[][] splitKeys = new byte[][] {
202       Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
203     };
204     HRegionInfo[] regions = MasterProcedureTestingUtility.createTable(
205       getMasterProcedureExecutor(), tableName, splitKeys, families);
206     // load and verify that there are rows in the table
207     MasterProcedureTestingUtility.loadData(
208       UTIL.getConnection(), tableName, 100, splitKeys, families);
209     assertEquals(100, UTIL.countRows(tableName));
210     // disable the table
211     UTIL.getHBaseAdmin().disableTable(tableName);
212 
213     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
214     ProcedureTestingUtility.waitNoProcedureRunning(procExec);
215     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
216 
217     // Start the Truncate procedure && kill the executor
218     long procId = procExec.submitProcedure(
219       new TruncateTableProcedure(procExec.getEnvironment(), tableName, preserveSplits));
220 
221     // Restart the executor and execute the step twice
222     // NOTE: the 7 (number of TruncateTableState steps) is hardcoded,
223     //       so you have to look at this test at least once when you add a new step.
224     MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(
225       procExec, procId, 7, TruncateTableState.values());
226 
227     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
228     UTIL.waitUntilAllRegionsAssigned(tableName);
229 
230     // validate the table regions and layout
231     regions = UTIL.getHBaseAdmin().getTableRegions(tableName).toArray(new HRegionInfo[0]);
232     if (preserveSplits) {
233       assertEquals(1 + splitKeys.length, regions.length);
234     } else {
235       assertEquals(1, regions.length);
236     }
237     MasterProcedureTestingUtility.validateTableCreation(
238       UTIL.getHBaseCluster().getMaster(), tableName, regions, families);
239 
240     // verify that there are no rows in the table
241     assertEquals(0, UTIL.countRows(tableName));
242 
243     // verify that the table is read/writable
244     MasterProcedureTestingUtility.loadData(
245       UTIL.getConnection(), tableName, 50, splitKeys, families);
246     assertEquals(50, UTIL.countRows(tableName));
247   }
248 
249   private ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() {
250     return UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor();
251   }
252 
253   @Test(timeout = 60000)
254   public void testTruncateWithPreserveAfterSplit() throws Exception {
255     final String[] families = new String[] { "f1", "f2" };
256     final byte[][] splitKeys =
257         new byte[][] { Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c") };
258     TableName tableName = TableName.valueOf("testTruncateWithPreserveAfterSplit");
259     HRegionInfo[] regions = MasterProcedureTestingUtility.createTable(getMasterProcedureExecutor(),
260       tableName, splitKeys, families);
261     splitAndTruncate(families, splitKeys, tableName, regions);
262   }
263 
264   @Test(timeout = 60000)
265   public void testTruncatePreserveWithReplicaRegionAfterSplit() throws Exception {
266     final String[] families = new String[] { "f1", "f2" };
267     final byte[][] splitKeys =
268         new byte[][] { Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c") };
269     TableName tableName = TableName.valueOf("testTruncateWithPreserveAfterSplit");
270     HTableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, families);
271     htd.setRegionReplication(3);
272     HRegionInfo[] regions =
273         MasterProcedureTestingUtility.createTable(getMasterProcedureExecutor(), htd, splitKeys);
274     splitAndTruncate(families, splitKeys, tableName, regions);
275   }
276 
277   private void splitAndTruncate(final String[] families, final byte[][] splitKeys,
278       TableName tableName, HRegionInfo[] regions) throws IOException, InterruptedException {
279     // load enough data so the table can split
280     MasterProcedureTestingUtility.loadData(UTIL.getConnection(), tableName, 5000, splitKeys,
281       families);
282     assertEquals(5000, UTIL.countRows(tableName));
283     UTIL.getHBaseAdmin().split(tableName);
284     UTIL.waitUntilAllRegionsAssigned(tableName);
285     // wait until split really happens
286     while (UTIL.getHBaseAdmin().getTableRegions(tableName).size() <= regions.length) {
287       Thread.sleep(50);
288     }
289     // disable the table
290     UTIL.getHBaseAdmin().disableTable(tableName);
291     // truncate the table
292     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
293     long procId = ProcedureTestingUtility.submitAndWait(procExec,
294       new TruncateTableProcedure(procExec.getEnvironment(), tableName, true));
295     ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
296 
297     UTIL.waitUntilAllRegionsAssigned(tableName);
298   }
299 
300   @Test
301   public void testOnHDFSFailurePreserveSplits() throws Exception {
302     final TableName tableName = TableName.valueOf("testOnHDFSFailurePreserveSplits");
303     testOnHDFSFailure(tableName, true);
304   }
305 
306   @Test
307   public void testOnHDFSFailureNoPreserveSplits() throws Exception {
308     final TableName tableName = TableName.valueOf("testOnHDFSFailureNoPreserveSplits");
309     testOnHDFSFailure(tableName, false);
310   }
311 
312   public static class TruncateTableProcedureOnHDFSFailure extends TruncateTableProcedure {
313 
314     private boolean failOnce = false;
315 
316     public TruncateTableProcedureOnHDFSFailure() {
317       // Required by the Procedure framework to create the procedure on replay
318       super();
319     }
320 
321     public TruncateTableProcedureOnHDFSFailure(final MasterProcedureEnv env, TableName tableName,
322       boolean preserveSplits)
323       throws HBaseIOException {
324       super(env, tableName, preserveSplits);
325     }
326 
327     @Override
328     protected Flow executeFromState(MasterProcedureEnv env,
329       MasterProcedureProtos.TruncateTableState state) throws InterruptedException {
330 
331       if (!failOnce &&
332         state == MasterProcedureProtos.TruncateTableState.TRUNCATE_TABLE_CREATE_FS_LAYOUT) {
333         try {
334           // To emulate an HDFS failure, create only the first region directory
335           HRegionInfo regionInfo = getFirstRegionInfo();
336           Configuration conf = env.getMasterConfiguration();
337           MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
338           Path tempdir = mfs.getTempDir();
339           Path tableDir = FSUtils.getTableDir(tempdir, regionInfo.getTable());
340           Path regionDir = new Path(tableDir,
341             ServerRegionReplicaUtil.getRegionInfoForFs(regionInfo).getEncodedName());
342           FileSystem fs = FileSystem.get(conf);
343           fs.mkdirs(regionDir);
344 
345           failOnce = true;
346           return Flow.HAS_MORE_STATE;
347         } catch (IOException e) {
348           fail("failed to create a region directory: " + e);
349         }
350       }
351 
352       return super.executeFromState(env, state);
353     }
354   }
355 
356   private void testOnHDFSFailure(TableName tableName, boolean preserveSplits) throws Exception {
357     String[] families = new String[] { "f1", "f2" };
358     byte[][] splitKeys = new byte[][] {
359       Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
360     };
361 
362     // create a table
363     MasterProcedureTestingUtility.createTable(
364       getMasterProcedureExecutor(), tableName, splitKeys, families);
365 
366     // load and verify that there are rows in the table
367     MasterProcedureTestingUtility.loadData(
368       UTIL.getConnection(), tableName, 100, splitKeys, families);
369     assertEquals(100, UTIL.countRows(tableName));
370 
371     // disable the table
372     UTIL.getHBaseAdmin().disableTable(tableName);
373 
374     // truncate the table
375     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
376     long procId = ProcedureTestingUtility.submitAndWait(procExec,
377       new TruncateTableProcedureOnHDFSFailure(procExec.getEnvironment(), tableName,
378         preserveSplits));
379     ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
380   }
381 }