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.assertTrue;
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.HRegionInfo;
28  import org.apache.hadoop.hbase.HTableDescriptor;
29  import org.apache.hadoop.hbase.InvalidFamilyOperationException;
30  import org.apache.hadoop.hbase.ProcedureInfo;
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.DeleteColumnFamilyState;
35  import org.apache.hadoop.hbase.testclassification.MediumTests;
36  import org.junit.After;
37  import org.junit.AfterClass;
38  import org.junit.Before;
39  import org.junit.BeforeClass;
40  import org.junit.Test;
41  import org.junit.experimental.categories.Category;
42  
43  @Category(MediumTests.class)
44  public class TestDeleteColumnFamilyProcedure {
45    private static final Log LOG = LogFactory.getLog(TestDeleteColumnFamilyProcedure.class);
46  
47    protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
48  
49    private static void setupConf(Configuration conf) {
50      conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
51    }
52  
53    @BeforeClass
54    public static void setupCluster() throws Exception {
55      setupConf(UTIL.getConfiguration());
56      UTIL.startMiniCluster(1);
57    }
58  
59    @AfterClass
60    public static void cleanupTest() throws Exception {
61      try {
62        UTIL.shutdownMiniCluster();
63      } catch (Exception e) {
64        LOG.warn("failure shutting down cluster", e);
65      }
66    }
67  
68    @Before
69    public void setup() throws Exception {
70      ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
71    }
72  
73    @After
74    public void tearDown() throws Exception {
75      ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
76      for (HTableDescriptor htd: UTIL.getHBaseAdmin().listTables()) {
77        LOG.info("Tear down, remove table=" + htd.getTableName());
78        UTIL.deleteTable(htd.getTableName());
79      }
80    }
81  
82    @Test(timeout = 60000)
83    public void testDeleteColumnFamily() throws Exception {
84      final TableName tableName = TableName.valueOf("testDeleteColumnFamily");
85      final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
86      final String cf1 = "cf1";
87      final String cf2 = "cf2";
88  
89      MasterProcedureTestingUtility.createTable(procExec, tableName, null, cf1, cf2, "f3");
90  
91      // Test 1: delete the column family that exists online
92      long procId1 = procExec.submitProcedure(
93        new DeleteColumnFamilyProcedure(procExec.getEnvironment(), tableName, cf1.getBytes()));
94      // Wait the completion
95      ProcedureTestingUtility.waitProcedure(procExec, procId1);
96      ProcedureTestingUtility.assertProcNotFailed(procExec, procId1);
97  
98      MasterProcedureTestingUtility.validateColumnFamilyDeletion(UTIL.getHBaseCluster().getMaster(),
99        tableName, cf1);
100 
101     // Test 2: delete the column family that exists offline
102     UTIL.getHBaseAdmin().disableTable(tableName);
103     long procId2 = procExec.submitProcedure(
104       new DeleteColumnFamilyProcedure(procExec.getEnvironment(), tableName, cf2.getBytes()));
105     // Wait the completion
106     ProcedureTestingUtility.waitProcedure(procExec, procId2);
107     ProcedureTestingUtility.assertProcNotFailed(procExec, procId2);
108   }
109 
110   @Test(timeout=60000)
111   public void testDeleteColumnFamilyTwice() throws Exception {
112     final TableName tableName = TableName.valueOf("testDeleteColumnFamilyTwice");
113     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
114 
115     final String cf2 = "cf2";
116 
117     MasterProcedureTestingUtility.createTable(procExec, tableName, null, "f1", cf2);
118 
119     // delete the column family that exists
120     long procId1 = procExec.submitProcedure(
121       new DeleteColumnFamilyProcedure(procExec.getEnvironment(), tableName, cf2.getBytes()));
122     // Wait the completion
123     ProcedureTestingUtility.waitProcedure(procExec, procId1);
124     // First delete should succeed
125     ProcedureTestingUtility.assertProcNotFailed(procExec, procId1);
126 
127     MasterProcedureTestingUtility.validateColumnFamilyDeletion(UTIL.getHBaseCluster().getMaster(),
128       tableName, cf2);
129 
130     // delete the column family that does not exist
131     long procId2 = procExec.submitProcedure(
132       new DeleteColumnFamilyProcedure(procExec.getEnvironment(), tableName, cf2.getBytes()));
133 
134     // Wait the completion
135     ProcedureTestingUtility.waitProcedure(procExec, procId2);
136 
137     // Second delete should fail with InvalidFamilyOperationException
138     ProcedureInfo result = procExec.getResult(procId2);
139     assertTrue(result.isFailed());
140     LOG.debug("Delete online failed with exception: " + result.getExceptionFullMessage());
141     assertTrue(
142       ProcedureTestingUtility.getExceptionCause(result) instanceof InvalidFamilyOperationException);
143 
144     // Try again, this time with table disabled.
145     UTIL.getHBaseAdmin().disableTable(tableName);
146     long procId3 = procExec.submitProcedure(
147       new DeleteColumnFamilyProcedure(procExec.getEnvironment(), tableName, cf2.getBytes()));
148     // Wait the completion
149     ProcedureTestingUtility.waitProcedure(procExec, procId3);
150     // Expect fail with InvalidFamilyOperationException
151     result = procExec.getResult(procId2);
152     assertTrue(result.isFailed());
153     LOG.debug("Delete offline failed with exception: " + result.getExceptionFullMessage());
154     assertTrue(
155       ProcedureTestingUtility.getExceptionCause(result) instanceof InvalidFamilyOperationException);
156   }
157 
158   @Test(timeout=60000)
159   public void testDeleteNonExistingColumnFamily() throws Exception {
160     final TableName tableName = TableName.valueOf("testDeleteNonExistingColumnFamily");
161     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
162 
163     final String cf3 = "cf3";
164 
165     MasterProcedureTestingUtility.createTable(procExec, tableName, null, "f1", "f2");
166 
167     // delete the column family that does not exist
168     long procId1 = procExec.submitProcedure(
169       new DeleteColumnFamilyProcedure(procExec.getEnvironment(), tableName, cf3.getBytes()));
170     // Wait the completion
171     ProcedureTestingUtility.waitProcedure(procExec, procId1);
172 
173     ProcedureInfo result = procExec.getResult(procId1);
174     assertTrue(result.isFailed());
175     LOG.debug("Delete failed with exception: " + result.getExceptionFullMessage());
176     assertTrue(
177       ProcedureTestingUtility.getExceptionCause(result) instanceof InvalidFamilyOperationException);
178   }
179 
180   @Test(timeout=60000)
181   public void testRecoveryAndDoubleExecutionOffline() throws Exception {
182     final TableName tableName = TableName.valueOf("testRecoveryAndDoubleExecutionOffline");
183     final String cf4 = "cf4";
184 
185     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
186 
187     // create the table
188     MasterProcedureTestingUtility.createTable(procExec, tableName, null, "f1", "f2", "f3", cf4);
189     UTIL.getHBaseAdmin().disableTable(tableName);
190     ProcedureTestingUtility.waitNoProcedureRunning(procExec);
191     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
192 
193     // Start the Delete procedure && kill the executor
194     long procId = procExec.submitProcedure(
195       new DeleteColumnFamilyProcedure(procExec.getEnvironment(), tableName, cf4.getBytes()));
196 
197     // Restart the executor and execute the step twice
198     int numberOfSteps = DeleteColumnFamilyState.values().length;
199     MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId, numberOfSteps,
200       DeleteColumnFamilyState.values());
201 
202     MasterProcedureTestingUtility.validateColumnFamilyDeletion(UTIL.getHBaseCluster().getMaster(),
203       tableName, cf4);
204   }
205 
206   @Test(timeout = 60000)
207   public void testRecoveryAndDoubleExecutionOnline() throws Exception {
208     final TableName tableName = TableName.valueOf("testRecoveryAndDoubleExecutionOnline");
209     final String cf5 = "cf5";
210 
211     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
212 
213     // create the table
214     MasterProcedureTestingUtility.createTable(procExec, tableName, null, "f1", "f2", "f3", cf5);
215     ProcedureTestingUtility.waitNoProcedureRunning(procExec);
216     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
217 
218     // Start the Delete procedure && kill the executor
219     long procId = procExec.submitProcedure(
220       new DeleteColumnFamilyProcedure(procExec.getEnvironment(), tableName, cf5.getBytes()));
221 
222     // Restart the executor and execute the step twice
223     int numberOfSteps = DeleteColumnFamilyState.values().length;
224     MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId, numberOfSteps,
225       DeleteColumnFamilyState.values());
226 
227     MasterProcedureTestingUtility.validateColumnFamilyDeletion(UTIL.getHBaseCluster().getMaster(),
228       tableName, cf5);
229   }
230 
231   @Test(timeout = 60000)
232   public void testRollbackAndDoubleExecution() throws Exception {
233     final TableName tableName = TableName.valueOf("testRollbackAndDoubleExecution");
234     final String cf5 = "cf5";
235 
236     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
237 
238     // create the table
239     HRegionInfo[] regions = MasterProcedureTestingUtility.createTable(
240       procExec, tableName, null, "f1", "f2", "f3", cf5);
241     ProcedureTestingUtility.waitNoProcedureRunning(procExec);
242     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
243 
244     // Start the Delete procedure && kill the executor
245     long procId = procExec.submitProcedure(
246       new DeleteColumnFamilyProcedure(procExec.getEnvironment(), tableName, cf5.getBytes()));
247 
248     // Failing before DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT we should trigger the rollback
249     // NOTE: the 1 (number before DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT step) is hardcoded,
250     //       so you have to look at this test at least once when you add a new step.
251     int numberOfSteps = 1;
252     MasterProcedureTestingUtility.testRollbackAndDoubleExecution(
253       procExec,
254       procId,
255       numberOfSteps,
256       DeleteColumnFamilyState.values());
257 
258     MasterProcedureTestingUtility.validateTableCreation(
259       UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2", "f3", cf5);
260   }
261 
262   @Test(timeout = 60000)
263   public void testRollbackAndDoubleExecutionAfterPONR() throws Exception {
264     final TableName tableName = TableName.valueOf("testRollbackAndDoubleExecutionAfterPONR");
265     final String cf5 = "cf5";
266 
267     final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
268 
269     // create the table
270     HRegionInfo[] regions = MasterProcedureTestingUtility.createTable(
271       procExec, tableName, null, "f1", "f2", "f3", cf5);
272     ProcedureTestingUtility.waitNoProcedureRunning(procExec);
273     ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
274 
275     // Start the Delete procedure && kill the executor
276     long procId = procExec.submitProcedure(
277       new DeleteColumnFamilyProcedure(procExec.getEnvironment(), tableName, cf5.getBytes()));
278 
279     // Failing after DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT we should not trigger the rollback.
280     // NOTE: the 4 (number of DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT + 1 step) is hardcoded,
281     //       so you have to look at this test at least once when you add a new step.
282     int numberOfSteps = 4;
283     MasterProcedureTestingUtility.testRollbackAndDoubleExecutionAfterPONR(
284       procExec,
285       procId,
286       numberOfSteps,
287       DeleteColumnFamilyState.values());
288 
289     MasterProcedureTestingUtility.validateColumnFamilyDeletion(
290       UTIL.getHBaseCluster().getMaster(), tableName, cf5);
291   }
292 
293   private ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() {
294     return UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor();
295   }
296 }