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