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  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.security.PrivilegedExceptionAction;
25  import java.util.List;
26  import java.util.concurrent.atomic.AtomicBoolean;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.hbase.HRegionInfo;
31  import org.apache.hadoop.hbase.HTableDescriptor;
32  import org.apache.hadoop.hbase.InvalidFamilyOperationException;
33  import org.apache.hadoop.hbase.TableName;
34  import org.apache.hadoop.hbase.classification.InterfaceAudience;
35  import org.apache.hadoop.hbase.executor.EventType;
36  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
37  import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
38  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
39  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
40  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.DeleteColumnFamilyState;
41  import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
42  import org.apache.hadoop.hbase.util.ByteStringer;
43  import org.apache.hadoop.hbase.util.Bytes;
44  import org.apache.hadoop.security.UserGroupInformation;
45  
46  /**
47   * The procedure to delete a column family from an existing table.
48   */
49  @InterfaceAudience.Private
50  public class DeleteColumnFamilyProcedure
51      extends StateMachineProcedure<MasterProcedureEnv, DeleteColumnFamilyState>
52      implements TableProcedureInterface {
53    private static final Log LOG = LogFactory.getLog(DeleteColumnFamilyProcedure.class);
54  
55    private final AtomicBoolean aborted = new AtomicBoolean(false);
56  
57    private HTableDescriptor unmodifiedHTableDescriptor;
58    private TableName tableName;
59    private byte [] familyName;
60    private UserGroupInformation user;
61  
62    private List<HRegionInfo> regionInfoList;
63    private Boolean traceEnabled;
64  
65    public DeleteColumnFamilyProcedure() {
66      this.unmodifiedHTableDescriptor = null;
67      this.regionInfoList = null;
68      this.traceEnabled = null;
69    }
70  
71    public DeleteColumnFamilyProcedure(
72        final MasterProcedureEnv env,
73        final TableName tableName,
74        final byte[] familyName) throws IOException {
75      this.tableName = tableName;
76      this.familyName = familyName;
77      this.user = env.getRequestUser().getUGI();
78      this.setOwner(this.user.getShortUserName());
79      this.unmodifiedHTableDescriptor = null;
80      this.regionInfoList = null;
81      this.traceEnabled = null;
82    }
83  
84    @Override
85    protected Flow executeFromState(final MasterProcedureEnv env, DeleteColumnFamilyState state) {
86      if (isTraceEnabled()) {
87        LOG.trace(this + " execute state=" + state);
88      }
89  
90      try {
91        switch (state) {
92        case DELETE_COLUMN_FAMILY_PREPARE:
93          prepareDelete(env);
94          setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_PRE_OPERATION);
95          break;
96        case DELETE_COLUMN_FAMILY_PRE_OPERATION:
97          preDelete(env, state);
98          setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR);
99          break;
100       case DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
101         updateTableDescriptor(env);
102         setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT);
103         break;
104       case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
105         deleteFromFs(env);
106         setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_POST_OPERATION);
107         break;
108       case DELETE_COLUMN_FAMILY_POST_OPERATION:
109         postDelete(env, state);
110         setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS);
111         break;
112       case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
113         reOpenAllRegionsIfTableIsOnline(env);
114         return Flow.NO_MORE_STATE;
115       default:
116         throw new UnsupportedOperationException(this + " unhandled state=" + state);
117       }
118     } catch (InterruptedException|IOException e) {
119       if (!isRollbackSupported(state)) {
120         // We reach a state that cannot be rolled back. We just need to keep retry.
121         LOG.warn("Error trying to delete the column family " + getColumnFamilyName()
122           + " from table " + tableName + "(in state=" + state + ")", e);
123       } else {
124         LOG.error("Error trying to delete the column family " + getColumnFamilyName()
125           + " from table " + tableName + "(in state=" + state + ")", e);
126         setFailure("master-delete-column-family", e);
127       }
128     }
129     return Flow.HAS_MORE_STATE;
130   }
131 
132   @Override
133   protected void rollbackState(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
134       throws IOException {
135     if (isTraceEnabled()) {
136       LOG.trace(this + " rollback state=" + state);
137     }
138     try {
139       switch (state) {
140       case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
141         break; // Nothing to undo.
142       case DELETE_COLUMN_FAMILY_POST_OPERATION:
143         // TODO-MAYBE: call the coprocessor event to undo?
144         break;
145       case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
146         // Once we reach to this state - we could NOT rollback - as it is tricky to undelete
147         // the deleted files. We are not suppose to reach here, throw exception so that we know
148         // there is a code bug to investigate.
149         throw new UnsupportedOperationException(this + " rollback of state=" + state
150             + " is unsupported.");
151       case DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
152         restoreTableDescriptor(env);
153         break;
154       case DELETE_COLUMN_FAMILY_PRE_OPERATION:
155         // TODO-MAYBE: call the coprocessor event to undo?
156         break;
157       case DELETE_COLUMN_FAMILY_PREPARE:
158         break; // nothing to do
159       default:
160         throw new UnsupportedOperationException(this + " unhandled state=" + state);
161       }
162     } catch (IOException e) {
163       // This will be retried. Unless there is a bug in the code,
164       // this should be just a "temporary error" (e.g. network down)
165       LOG.warn("Failed rollback attempt step " + state + " for deleting the column family"
166           + getColumnFamilyName() + " to the table " + tableName, e);
167       throw e;
168     }
169   }
170 
171   @Override
172   protected DeleteColumnFamilyState getState(final int stateId) {
173     return DeleteColumnFamilyState.valueOf(stateId);
174   }
175 
176   @Override
177   protected int getStateId(final DeleteColumnFamilyState state) {
178     return state.getNumber();
179   }
180 
181   @Override
182   protected DeleteColumnFamilyState getInitialState() {
183     return DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_PREPARE;
184   }
185 
186   @Override
187   protected void setNextState(DeleteColumnFamilyState state) {
188     if (aborted.get() && isRollbackSupported(state)) {
189       setAbortFailure("delete-columnfamily", "abort requested");
190     } else {
191       super.setNextState(state);
192     }
193   }
194 
195   @Override
196   public boolean abort(final MasterProcedureEnv env) {
197     aborted.set(true);
198     return true;
199   }
200 
201   @Override
202   protected boolean acquireLock(final MasterProcedureEnv env) {
203     if (!env.isInitialized()) return false;
204     return env.getProcedureQueue().tryAcquireTableWrite(
205       tableName,
206       EventType.C_M_DELETE_FAMILY.toString());
207   }
208 
209   @Override
210   protected void releaseLock(final MasterProcedureEnv env) {
211     env.getProcedureQueue().releaseTableWrite(tableName);
212   }
213 
214   @Override
215   public void serializeStateData(final OutputStream stream) throws IOException {
216     super.serializeStateData(stream);
217 
218     MasterProcedureProtos.DeleteColumnFamilyStateData.Builder deleteCFMsg =
219         MasterProcedureProtos.DeleteColumnFamilyStateData.newBuilder()
220             .setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
221             .setTableName(ProtobufUtil.toProtoTableName(tableName))
222             .setColumnfamilyName(ByteStringer.wrap(familyName));
223     if (unmodifiedHTableDescriptor != null) {
224       deleteCFMsg.setUnmodifiedTableSchema(unmodifiedHTableDescriptor.convert());
225     }
226 
227     deleteCFMsg.build().writeDelimitedTo(stream);
228   }
229 
230   @Override
231   public void deserializeStateData(final InputStream stream) throws IOException {
232     super.deserializeStateData(stream);
233     MasterProcedureProtos.DeleteColumnFamilyStateData deleteCFMsg =
234         MasterProcedureProtos.DeleteColumnFamilyStateData.parseDelimitedFrom(stream);
235     user = MasterProcedureUtil.toUserInfo(deleteCFMsg.getUserInfo());
236     tableName = ProtobufUtil.toTableName(deleteCFMsg.getTableName());
237     familyName = deleteCFMsg.getColumnfamilyName().toByteArray();
238 
239     if (deleteCFMsg.hasUnmodifiedTableSchema()) {
240       unmodifiedHTableDescriptor = HTableDescriptor.convert(deleteCFMsg.getUnmodifiedTableSchema());
241     }
242   }
243 
244   @Override
245   public void toStringClassDetails(StringBuilder sb) {
246     sb.append(getClass().getSimpleName());
247     sb.append(" (table=");
248     sb.append(tableName);
249     sb.append(", columnfamily=");
250     if (familyName != null) {
251       sb.append(getColumnFamilyName());
252     } else {
253       sb.append("Unknown");
254     }
255     sb.append(")");
256   }
257 
258   @Override
259   public TableName getTableName() {
260     return tableName;
261   }
262 
263   @Override
264   public TableOperationType getTableOperationType() {
265     return TableOperationType.EDIT;
266   }
267 
268   /**
269    * Action before any real action of deleting column family.
270    * @param env MasterProcedureEnv
271    * @throws IOException
272    */
273   private void prepareDelete(final MasterProcedureEnv env) throws IOException {
274     // Checks whether the table is allowed to be modified.
275     MasterDDLOperationHelper.checkTableModifiable(env, tableName);
276 
277     // In order to update the descriptor, we need to retrieve the old descriptor for comparison.
278     unmodifiedHTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
279     if (unmodifiedHTableDescriptor == null) {
280       throw new IOException("HTableDescriptor missing for " + tableName);
281     }
282     if (!unmodifiedHTableDescriptor.hasFamily(familyName)) {
283       throw new InvalidFamilyOperationException("Family '" + getColumnFamilyName()
284           + "' does not exist, so it cannot be deleted");
285     }
286 
287     if (unmodifiedHTableDescriptor.getColumnFamilies().length == 1) {
288       throw new InvalidFamilyOperationException("Family '" + getColumnFamilyName()
289         + "' is the only column family in the table, so it cannot be deleted");
290     }
291   }
292 
293   /**
294    * Action before deleting column family.
295    * @param env MasterProcedureEnv
296    * @param state the procedure state
297    * @throws IOException
298    * @throws InterruptedException
299    */
300   private void preDelete(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
301       throws IOException, InterruptedException {
302     runCoprocessorAction(env, state);
303   }
304 
305   /**
306    * Remove the column family from the file system and update the table descriptor
307    */
308   private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException {
309     // Update table descriptor
310     LOG.info("DeleteColumn. Table = " + tableName + " family = " + getColumnFamilyName());
311 
312     HTableDescriptor htd = env.getMasterServices().getTableDescriptors().get(tableName);
313 
314     if (!htd.hasFamily(familyName)) {
315       // It is possible to reach this situation, as we could already delete the column family
316       // from table descriptor, but the master failover happens before we complete this state.
317       // We should be able to handle running this function multiple times without causing problem.
318       return;
319     }
320 
321     htd.removeFamily(familyName);
322     env.getMasterServices().getTableDescriptors().add(htd);
323   }
324 
325   /**
326    * Restore back to the old descriptor
327    * @param env MasterProcedureEnv
328    * @throws IOException
329    **/
330   private void restoreTableDescriptor(final MasterProcedureEnv env) throws IOException {
331     env.getMasterServices().getTableDescriptors().add(unmodifiedHTableDescriptor);
332 
333     // Make sure regions are opened after table descriptor is updated.
334     reOpenAllRegionsIfTableIsOnline(env);
335   }
336 
337   /**
338    * Remove the column family from the file system
339    **/
340   private void deleteFromFs(final MasterProcedureEnv env) throws IOException {
341     MasterDDLOperationHelper.deleteColumnFamilyFromFileSystem(env, tableName,
342       getRegionInfoList(env), familyName);
343   }
344 
345   /**
346    * Action after deleting column family.
347    * @param env MasterProcedureEnv
348    * @param state the procedure state
349    * @throws IOException
350    * @throws InterruptedException
351    */
352   private void postDelete(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
353       throws IOException, InterruptedException {
354     runCoprocessorAction(env, state);
355   }
356 
357   /**
358    * Last action from the procedure - executed when online schema change is supported.
359    * @param env MasterProcedureEnv
360    * @throws IOException
361    */
362   private void reOpenAllRegionsIfTableIsOnline(final MasterProcedureEnv env) throws IOException {
363     // This operation only run when the table is enabled.
364     if (!env.getMasterServices().getAssignmentManager().getTableStateManager()
365         .isTableState(getTableName(), ZooKeeperProtos.Table.State.ENABLED)) {
366       return;
367     }
368 
369     if (MasterDDLOperationHelper.reOpenAllRegions(env, getTableName(), getRegionInfoList(env))) {
370       LOG.info("Completed delete column family operation on table " + getTableName());
371     } else {
372       LOG.warn("Error on reopening the regions on table " + getTableName());
373     }
374   }
375 
376   /**
377    * The procedure could be restarted from a different machine. If the variable is null, we need to
378    * retrieve it.
379    * @return traceEnabled
380    */
381   private Boolean isTraceEnabled() {
382     if (traceEnabled == null) {
383       traceEnabled = LOG.isTraceEnabled();
384     }
385     return traceEnabled;
386   }
387 
388   private String getColumnFamilyName() {
389     return Bytes.toString(familyName);
390   }
391 
392   /**
393    * Coprocessor Action.
394    * @param env MasterProcedureEnv
395    * @param state the procedure state
396    * @throws IOException
397    * @throws InterruptedException
398    */
399   private void runCoprocessorAction(final MasterProcedureEnv env,
400       final DeleteColumnFamilyState state) throws IOException, InterruptedException {
401     final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
402     if (cpHost != null) {
403       user.doAs(new PrivilegedExceptionAction<Void>() {
404         @Override
405         public Void run() throws Exception {
406           switch (state) {
407           case DELETE_COLUMN_FAMILY_PRE_OPERATION:
408             cpHost.preDeleteColumnHandler(tableName, familyName);
409             break;
410           case DELETE_COLUMN_FAMILY_POST_OPERATION:
411             cpHost.postDeleteColumnHandler(tableName, familyName);
412             break;
413           default:
414             throw new UnsupportedOperationException(this + " unhandled state=" + state);
415           }
416           return null;
417         }
418       });
419     }
420   }
421 
422   /*
423    * Check whether we are in the state that can be rollback
424    */
425   private boolean isRollbackSupported(final DeleteColumnFamilyState state) {
426     switch (state) {
427     case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
428     case DELETE_COLUMN_FAMILY_POST_OPERATION:
429     case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
430         // It is not safe to rollback if we reach to these states.
431         return false;
432       default:
433         break;
434     }
435     return true;
436   }
437 
438   private List<HRegionInfo> getRegionInfoList(final MasterProcedureEnv env) throws IOException {
439     if (regionInfoList == null) {
440       regionInfoList = ProcedureSyncWait.getRegionsFromMeta(env, getTableName());
441     }
442     return regionInfoList;
443   }
444 }