001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master.procedure;
019
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.List;
023import org.apache.hadoop.conf.Configuration;
024import org.apache.hadoop.fs.FileSystem;
025import org.apache.hadoop.fs.Path;
026import org.apache.hadoop.hbase.MetaTableAccessor;
027import org.apache.hadoop.hbase.TableName;
028import org.apache.hadoop.hbase.TableNotDisabledException;
029import org.apache.hadoop.hbase.TableNotFoundException;
030import org.apache.hadoop.hbase.backup.HFileArchiver;
031import org.apache.hadoop.hbase.client.Delete;
032import org.apache.hadoop.hbase.client.RegionInfo;
033import org.apache.hadoop.hbase.client.RegionReplicaUtil;
034import org.apache.hadoop.hbase.client.Result;
035import org.apache.hadoop.hbase.client.ResultScanner;
036import org.apache.hadoop.hbase.client.Scan;
037import org.apache.hadoop.hbase.client.Table;
038import org.apache.hadoop.hbase.favored.FavoredNodesManager;
039import org.apache.hadoop.hbase.filter.KeyOnlyFilter;
040import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
041import org.apache.hadoop.hbase.master.MasterFileSystem;
042import org.apache.hadoop.hbase.mob.MobConstants;
043import org.apache.hadoop.hbase.mob.MobUtils;
044import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
045import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException;
046import org.apache.hadoop.hbase.procedure2.ProcedureUtil;
047import org.apache.hadoop.hbase.util.CommonFSUtils;
048import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
049import org.apache.hadoop.hbase.util.FSUtils;
050import org.apache.hadoop.hbase.util.RetryCounter;
051import org.apache.yetus.audience.InterfaceAudience;
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils;
056
057import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
058import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
059import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
060import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.DeleteTableState;
061import org.apache.hadoop.hbase.shaded.protobuf.generated.ProcedureProtos;
062
063@InterfaceAudience.Private
064public class DeleteTableProcedure extends AbstractStateMachineTableProcedure<DeleteTableState> {
065  private static final Logger LOG = LoggerFactory.getLogger(DeleteTableProcedure.class);
066
067  private List<RegionInfo> regions;
068  private TableName tableName;
069  private RetryCounter retryCounter;
070
071  public DeleteTableProcedure() {
072    // Required by the Procedure framework to create the procedure on replay
073    super();
074  }
075
076  public DeleteTableProcedure(final MasterProcedureEnv env, final TableName tableName) {
077    this(env, tableName, null);
078  }
079
080  public DeleteTableProcedure(final MasterProcedureEnv env, final TableName tableName,
081    final ProcedurePrepareLatch syncLatch) {
082    super(env, syncLatch);
083    this.tableName = tableName;
084  }
085
086  @Override
087  protected Flow executeFromState(final MasterProcedureEnv env, DeleteTableState state)
088    throws InterruptedException, ProcedureSuspendedException {
089    if (LOG.isTraceEnabled()) {
090      LOG.trace(this + " execute state=" + state);
091    }
092    try {
093      switch (state) {
094        case DELETE_TABLE_PRE_OPERATION:
095          // Verify if we can delete the table
096          boolean deletable = prepareDelete(env);
097          releaseSyncLatch();
098          if (!deletable) {
099            assert isFailed() : "the delete should have an exception here";
100            return Flow.NO_MORE_STATE;
101          }
102
103          // TODO: Move out... in the acquireLock()
104          LOG.debug("Waiting for RIT for {}", this);
105          regions = env.getAssignmentManager().getRegionStates()
106            .getRegionsOfTableForDeleting(getTableName());
107          assert regions != null && !regions.isEmpty() : "unexpected 0 regions";
108          ProcedureSyncWait.waitRegionInTransition(env, regions);
109
110          // Call coprocessors
111          preDelete(env);
112
113          setNextState(DeleteTableState.DELETE_TABLE_CLEAR_FS_LAYOUT);
114          break;
115        case DELETE_TABLE_CLEAR_FS_LAYOUT:
116          LOG.debug("Deleting regions from filesystem for {}", this);
117          DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true);
118          setNextState(DeleteTableState.DELETE_TABLE_REMOVE_FROM_META);
119          break;
120        case DELETE_TABLE_REMOVE_FROM_META:
121          LOG.debug("Deleting regions from META for {}", this);
122          DeleteTableProcedure.deleteFromMeta(env, getTableName(), regions);
123          setNextState(DeleteTableState.DELETE_TABLE_UNASSIGN_REGIONS);
124          regions = null;
125          break;
126        case DELETE_TABLE_UNASSIGN_REGIONS:
127          LOG.debug("Deleting assignment state for {}", this);
128          DeleteTableProcedure.deleteAssignmentState(env, getTableName());
129          setNextState(DeleteTableState.DELETE_TABLE_POST_OPERATION);
130          break;
131        case DELETE_TABLE_POST_OPERATION:
132          postDelete(env);
133          retryCounter = null;
134          LOG.debug("Finished {}", this);
135          return Flow.NO_MORE_STATE;
136        default:
137          throw new UnsupportedOperationException("unhandled state=" + state);
138      }
139    } catch (IOException e) {
140      if (isRollbackSupported(state)) {
141        setFailure("master-delete-table", e);
142      } else {
143        if (retryCounter == null) {
144          retryCounter = ProcedureUtil.createRetryCounter(env.getMasterConfiguration());
145        }
146        long backoff = retryCounter.getBackoffTimeAndIncrementAttempts();
147        LOG.warn("Retriable error trying to delete table={},state={},suspend {}secs.",
148          getTableName(), state, backoff / 1000, e);
149        throw suspend(Math.toIntExact(backoff), true);
150      }
151    }
152    retryCounter = null;
153    return Flow.HAS_MORE_STATE;
154  }
155
156  @Override
157  protected synchronized boolean setTimeoutFailure(MasterProcedureEnv env) {
158    setState(ProcedureProtos.ProcedureState.RUNNABLE);
159    env.getProcedureScheduler().addFront(this);
160    return false;
161  }
162
163  @Override
164  protected boolean abort(MasterProcedureEnv env) {
165    // TODO: Current behavior is: with no rollback and no abort support, procedure may get stuck
166    // looping in retrying failing a step forever. Default behavior of abort is changed to support
167    // aborting all procedures. Override the default wisely. Following code retains the current
168    // behavior. Revisit it later.
169    return isRollbackSupported(getCurrentState()) ? super.abort(env) : false;
170  }
171
172  @Override
173  protected void rollbackState(final MasterProcedureEnv env, final DeleteTableState state) {
174    if (state == DeleteTableState.DELETE_TABLE_PRE_OPERATION) {
175      // nothing to rollback, pre-delete is just table-state checks.
176      // We can fail if the table does not exist or is not disabled.
177      // TODO: coprocessor rollback semantic is still undefined.
178      releaseSyncLatch();
179      return;
180    }
181
182    // The delete doesn't have a rollback. The execution will succeed, at some point.
183    throw new UnsupportedOperationException("unhandled state=" + state);
184  }
185
186  @Override
187  protected boolean isRollbackSupported(final DeleteTableState state) {
188    switch (state) {
189      case DELETE_TABLE_PRE_OPERATION:
190        return true;
191      default:
192        return false;
193    }
194  }
195
196  @Override
197  protected DeleteTableState getState(final int stateId) {
198    return DeleteTableState.forNumber(stateId);
199  }
200
201  @Override
202  protected int getStateId(final DeleteTableState state) {
203    return state.getNumber();
204  }
205
206  @Override
207  protected DeleteTableState getInitialState() {
208    return DeleteTableState.DELETE_TABLE_PRE_OPERATION;
209  }
210
211  @Override
212  protected boolean holdLock(MasterProcedureEnv env) {
213    return true;
214  }
215
216  @Override
217  public TableName getTableName() {
218    return tableName;
219  }
220
221  @Override
222  public TableOperationType getTableOperationType() {
223    return TableOperationType.DELETE;
224  }
225
226  @Override
227  protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException {
228    super.serializeStateData(serializer);
229
230    MasterProcedureProtos.DeleteTableStateData.Builder state =
231      MasterProcedureProtos.DeleteTableStateData.newBuilder()
232        .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser()))
233        .setTableName(ProtobufUtil.toProtoTableName(tableName));
234    if (regions != null) {
235      for (RegionInfo hri : regions) {
236        state.addRegionInfo(ProtobufUtil.toRegionInfo(hri));
237      }
238    }
239    serializer.serialize(state.build());
240  }
241
242  @Override
243  protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException {
244    super.deserializeStateData(serializer);
245
246    MasterProcedureProtos.DeleteTableStateData state =
247      serializer.deserialize(MasterProcedureProtos.DeleteTableStateData.class);
248    setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo()));
249    tableName = ProtobufUtil.toTableName(state.getTableName());
250    if (state.getRegionInfoCount() == 0) {
251      regions = null;
252    } else {
253      regions = new ArrayList<>(state.getRegionInfoCount());
254      for (HBaseProtos.RegionInfo hri : state.getRegionInfoList()) {
255        regions.add(ProtobufUtil.toRegionInfo(hri));
256      }
257    }
258  }
259
260  private boolean prepareDelete(final MasterProcedureEnv env) throws IOException {
261    try {
262      env.getMasterServices().checkTableModifiable(tableName);
263    } catch (TableNotFoundException | TableNotDisabledException e) {
264      setFailure("master-delete-table", e);
265      return false;
266    }
267    return true;
268  }
269
270  private boolean preDelete(final MasterProcedureEnv env) throws IOException, InterruptedException {
271    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
272    if (cpHost != null) {
273      final TableName tableName = this.tableName;
274      cpHost.preDeleteTableAction(tableName, getUser());
275    }
276    return true;
277  }
278
279  private void postDelete(final MasterProcedureEnv env) throws IOException, InterruptedException {
280    deleteTableStates(env, tableName);
281
282    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
283    if (cpHost != null) {
284      final TableName tableName = this.tableName;
285      cpHost.postCompletedDeleteTableAction(tableName, getUser());
286    }
287  }
288
289  protected static void deleteFromFs(final MasterProcedureEnv env, final TableName tableName,
290    final List<RegionInfo> regions, final boolean archive) throws IOException {
291    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
292    final FileSystem fs = mfs.getFileSystem();
293    final Configuration conf = env.getMasterConfiguration();
294
295    final Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), tableName);
296
297    if (fs.exists(tableDir)) {
298      // Archive regions from FS (temp directory)
299      if (archive) {
300        List<Path> regionDirList = new ArrayList<>();
301        for (RegionInfo region : regions) {
302          if (RegionReplicaUtil.isDefaultReplica(region)) {
303            regionDirList.add(FSUtils.getRegionDirFromTableDir(tableDir, region));
304            List<RegionInfo> mergeRegions =
305              env.getAssignmentManager().getRegionStateStore().getMergeRegions(region);
306            if (!CollectionUtils.isEmpty(mergeRegions)) {
307              mergeRegions.stream()
308                .forEach(r -> regionDirList.add(FSUtils.getRegionDirFromTableDir(tableDir, r)));
309            }
310          }
311        }
312        HFileArchiver.archiveRegions(conf, fs, mfs.getRootDir(), tableDir, regionDirList);
313        if (!regionDirList.isEmpty()) {
314          LOG.debug("Archived {} regions", tableName);
315        }
316      }
317
318      // Archive mob data
319      Path mobTableDir =
320        CommonFSUtils.getTableDir(new Path(mfs.getRootDir(), MobConstants.MOB_DIR_NAME), tableName);
321      Path regionDir = new Path(mobTableDir, MobUtils.getMobRegionInfo(tableName).getEncodedName());
322      if (fs.exists(regionDir)) {
323        HFileArchiver.archiveRegion(conf, fs, mfs.getRootDir(), mobTableDir, regionDir);
324      }
325
326      // Delete table directory from FS
327      if (!fs.delete(tableDir, true) && fs.exists(tableDir)) {
328        throw new IOException("Couldn't delete " + tableDir);
329      }
330
331      // Delete the table directory where the mob files are saved
332      if (mobTableDir != null && fs.exists(mobTableDir)) {
333        if (!fs.delete(mobTableDir, true)) {
334          throw new IOException("Couldn't delete mob dir " + mobTableDir);
335        }
336      }
337
338      // Delete the directory on wal filesystem
339      FileSystem walFs = mfs.getWALFileSystem();
340      Path tableWALDir = CommonFSUtils.getWALTableDir(env.getMasterConfiguration(), tableName);
341      if (walFs.exists(tableWALDir) && !walFs.delete(tableWALDir, true)) {
342        throw new IOException("Couldn't delete table dir on wal filesystem" + tableWALDir);
343      }
344    }
345  }
346
347  /**
348   * There may be items for this table still up in hbase:meta in the case where the info:regioninfo
349   * column was empty because of some write error. Remove ALL rows from hbase:meta that have to do
350   * with this table.
351   * <p/>
352   * See HBASE-12980.
353   */
354  private static void cleanRegionsInMeta(final MasterProcedureEnv env, final TableName tableName)
355    throws IOException {
356    Scan tableScan = MetaTableAccessor.getScanForTableName(env.getMasterConfiguration(), tableName)
357      .setFilter(new KeyOnlyFilter());
358    long now = EnvironmentEdgeManager.currentTime();
359    List<Delete> deletes = new ArrayList<>();
360    try (
361      Table metaTable = env.getMasterServices().getConnection().getTable(TableName.META_TABLE_NAME);
362      ResultScanner scanner = metaTable.getScanner(tableScan)) {
363      for (;;) {
364        Result result = scanner.next();
365        if (result == null) {
366          break;
367        }
368        deletes.add(new Delete(result.getRow(), now));
369      }
370      if (!deletes.isEmpty()) {
371        LOG.warn("Deleting some vestigial " + deletes.size() + " rows of " + tableName + " from "
372          + TableName.META_TABLE_NAME);
373        metaTable.delete(deletes);
374      }
375    }
376  }
377
378  protected static void deleteFromMeta(final MasterProcedureEnv env, final TableName tableName,
379    List<RegionInfo> regions) throws IOException {
380    // Clean any remaining rows for this table.
381    cleanRegionsInMeta(env, tableName);
382
383    // clean region references from the server manager
384    env.getMasterServices().getServerManager().removeRegions(regions);
385
386    // Clear Favored Nodes for this table
387    FavoredNodesManager fnm = env.getMasterServices().getFavoredNodesManager();
388    if (fnm != null) {
389      fnm.deleteFavoredNodesForRegions(regions);
390    }
391
392    deleteTableDescriptorCache(env, tableName);
393  }
394
395  protected static void deleteAssignmentState(final MasterProcedureEnv env,
396    final TableName tableName) throws IOException {
397    // Clean up regions of the table in RegionStates.
398    LOG.debug("Removing '" + tableName + "' from region states.");
399    env.getMasterServices().getAssignmentManager().deleteTable(tableName);
400
401    // If entry for this table states, remove it.
402    LOG.debug("Marking '" + tableName + "' as deleted.");
403    env.getMasterServices().getTableStateManager().setDeletedTable(tableName);
404  }
405
406  protected static void deleteTableDescriptorCache(final MasterProcedureEnv env,
407    final TableName tableName) throws IOException {
408    LOG.debug("Removing '" + tableName + "' descriptor.");
409    env.getMasterServices().getTableDescriptors().remove(tableName);
410  }
411
412  protected static void deleteTableStates(final MasterProcedureEnv env, final TableName tableName)
413    throws IOException {
414    if (!tableName.isSystemTable()) {
415      ProcedureSyncWait.getMasterQuotaManager(env).removeTableFromNamespaceQuota(tableName);
416    }
417  }
418}