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