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  private String recoverySnapshotName;
071
072  public DeleteTableProcedure() {
073    // Required by the Procedure framework to create the procedure on replay
074    super();
075  }
076
077  public DeleteTableProcedure(final MasterProcedureEnv env, final TableName tableName) {
078    this(env, tableName, null);
079  }
080
081  public DeleteTableProcedure(final MasterProcedureEnv env, final TableName tableName,
082    final ProcedurePrepareLatch syncLatch) {
083    super(env, syncLatch);
084    this.tableName = tableName;
085  }
086
087  @Override
088  protected Flow executeFromState(final MasterProcedureEnv env, DeleteTableState state)
089    throws InterruptedException, ProcedureSuspendedException {
090    if (LOG.isTraceEnabled()) {
091      LOG.trace(this + " execute state=" + state);
092    }
093    try {
094      switch (state) {
095        case DELETE_TABLE_PRE_OPERATION:
096          // Verify if we can delete the table
097          boolean deletable = prepareDelete(env);
098          releaseSyncLatch();
099          if (!deletable) {
100            assert isFailed() : "the delete should have an exception here";
101            return Flow.NO_MORE_STATE;
102          }
103
104          // TODO: Move out... in the acquireLock()
105          LOG.debug("Waiting for RIT for {}", this);
106          regions = env.getAssignmentManager().getRegionStates()
107            .getRegionsOfTableForDeleting(getTableName());
108          assert regions != null && !regions.isEmpty() : "unexpected 0 regions";
109          ProcedureSyncWait.waitRegionInTransition(env, regions);
110
111          // Call coprocessors
112          preDelete(env);
113
114          // Check if we should create a recover snapshot
115          if (RecoverySnapshotUtils.isRecoveryEnabled(env)) {
116            setNextState(DeleteTableState.DELETE_TABLE_SNAPSHOT);
117          } else {
118            setNextState(DeleteTableState.DELETE_TABLE_CLEAR_FS_LAYOUT);
119          }
120          break;
121        case DELETE_TABLE_SNAPSHOT:
122          // Create recovery snapshot procedure as child procedure
123          recoverySnapshotName = RecoverySnapshotUtils.generateSnapshotName(getTableName());
124          SnapshotProcedure snapshotProcedure =
125            RecoverySnapshotUtils.createSnapshotProcedure(env, getTableName(), recoverySnapshotName,
126              env.getMasterServices().getTableDescriptors().get(tableName));
127          // Submit snapshot procedure as child procedure
128          addChildProcedure(snapshotProcedure);
129          LOG.debug("Creating recovery snapshot {} for table {} before deletion",
130            recoverySnapshotName, getTableName());
131          setNextState(DeleteTableState.DELETE_TABLE_CLEAR_FS_LAYOUT);
132          break;
133        case DELETE_TABLE_CLEAR_FS_LAYOUT:
134          LOG.debug("Deleting regions from filesystem for {}", this);
135          DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true);
136          setNextState(DeleteTableState.DELETE_TABLE_REMOVE_FROM_META);
137          break;
138        case DELETE_TABLE_REMOVE_FROM_META:
139          LOG.debug("Deleting regions from META for {}", this);
140          DeleteTableProcedure.deleteFromMeta(env, getTableName(), regions);
141          setNextState(DeleteTableState.DELETE_TABLE_UNASSIGN_REGIONS);
142          regions = null;
143          break;
144        case DELETE_TABLE_UNASSIGN_REGIONS:
145          LOG.debug("Deleting assignment state for {}", this);
146          DeleteTableProcedure.deleteAssignmentState(env, getTableName());
147          setNextState(DeleteTableState.DELETE_TABLE_POST_OPERATION);
148          break;
149        case DELETE_TABLE_POST_OPERATION:
150          postDelete(env);
151          retryCounter = null;
152          LOG.debug("Finished {}", this);
153          return Flow.NO_MORE_STATE;
154        default:
155          throw new UnsupportedOperationException("unhandled state=" + state);
156      }
157    } catch (IOException e) {
158      if (isRollbackSupported(state)) {
159        setFailure("master-delete-table", e);
160      } else {
161        if (retryCounter == null) {
162          retryCounter = ProcedureUtil.createRetryCounter(env.getMasterConfiguration());
163        }
164        long backoff = retryCounter.getBackoffTimeAndIncrementAttempts();
165        LOG.warn("Retriable error trying to delete table={},state={},suspend {}secs.",
166          getTableName(), state, backoff / 1000, e);
167        throw suspend(Math.toIntExact(backoff), true);
168      }
169    }
170    retryCounter = null;
171    return Flow.HAS_MORE_STATE;
172  }
173
174  @Override
175  protected synchronized boolean setTimeoutFailure(MasterProcedureEnv env) {
176    setState(ProcedureProtos.ProcedureState.RUNNABLE);
177    env.getProcedureScheduler().addFront(this);
178    return false;
179  }
180
181  @Override
182  protected boolean abort(MasterProcedureEnv env) {
183    // TODO: Current behavior is: with no rollback and no abort support, procedure may get stuck
184    // looping in retrying failing a step forever. Default behavior of abort is changed to support
185    // aborting all procedures. Override the default wisely. Following code retains the current
186    // behavior. Revisit it later.
187    return isRollbackSupported(getCurrentState()) ? super.abort(env) : false;
188  }
189
190  @Override
191  protected void rollbackState(final MasterProcedureEnv env, final DeleteTableState state) {
192    switch (state) {
193      case DELETE_TABLE_PRE_OPERATION:
194        // nothing to rollback, pre-delete is just table-state checks.
195        // We can fail if the table does not exist or is not disabled.
196        // TODO: coprocessor rollback semantic is still undefined.
197        releaseSyncLatch();
198        return;
199      case DELETE_TABLE_SNAPSHOT:
200        // Handle recovery snapshot rollback. There is no DeleteSnapshotProcedure as such to use
201        // here directly as a child procedure, so we call a utility method to delete the snapshot
202        // which uses the SnapshotManager to delete the snapshot.
203        if (recoverySnapshotName != null) {
204          RecoverySnapshotUtils.deleteRecoverySnapshot(env, recoverySnapshotName, getTableName());
205          recoverySnapshotName = null;
206        }
207        return;
208      default:
209        // Delete from other states doesn't have a rollback. The execution will succeed, at some
210        // point.
211        throw new UnsupportedOperationException("unhandled state=" + state);
212    }
213  }
214
215  @Override
216  protected boolean isRollbackSupported(final DeleteTableState state) {
217    switch (state) {
218      case DELETE_TABLE_PRE_OPERATION:
219      case DELETE_TABLE_SNAPSHOT:
220        return true;
221      default:
222        return false;
223    }
224  }
225
226  @Override
227  protected DeleteTableState getState(final int stateId) {
228    return DeleteTableState.forNumber(stateId);
229  }
230
231  @Override
232  protected int getStateId(final DeleteTableState state) {
233    return state.getNumber();
234  }
235
236  @Override
237  protected DeleteTableState getInitialState() {
238    return DeleteTableState.DELETE_TABLE_PRE_OPERATION;
239  }
240
241  @Override
242  protected boolean holdLock(MasterProcedureEnv env) {
243    return true;
244  }
245
246  @Override
247  public TableName getTableName() {
248    return tableName;
249  }
250
251  @Override
252  public TableOperationType getTableOperationType() {
253    return TableOperationType.DELETE;
254  }
255
256  @Override
257  protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException {
258    super.serializeStateData(serializer);
259
260    MasterProcedureProtos.DeleteTableStateData.Builder state =
261      MasterProcedureProtos.DeleteTableStateData.newBuilder()
262        .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser()))
263        .setTableName(ProtobufUtil.toProtoTableName(tableName));
264    if (regions != null) {
265      for (RegionInfo hri : regions) {
266        state.addRegionInfo(ProtobufUtil.toRegionInfo(hri));
267      }
268    }
269    if (recoverySnapshotName != null) {
270      state.setSnapshotName(recoverySnapshotName);
271    }
272    serializer.serialize(state.build());
273  }
274
275  @Override
276  protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException {
277    super.deserializeStateData(serializer);
278
279    MasterProcedureProtos.DeleteTableStateData state =
280      serializer.deserialize(MasterProcedureProtos.DeleteTableStateData.class);
281    setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo()));
282    tableName = ProtobufUtil.toTableName(state.getTableName());
283    if (state.getRegionInfoCount() == 0) {
284      regions = null;
285    } else {
286      regions = new ArrayList<>(state.getRegionInfoCount());
287      for (HBaseProtos.RegionInfo hri : state.getRegionInfoList()) {
288        regions.add(ProtobufUtil.toRegionInfo(hri));
289      }
290    }
291    if (state.hasSnapshotName()) {
292      recoverySnapshotName = state.getSnapshotName();
293    }
294  }
295
296  private boolean prepareDelete(final MasterProcedureEnv env) throws IOException {
297    try {
298      env.getMasterServices().checkTableModifiable(tableName);
299    } catch (TableNotFoundException | TableNotDisabledException e) {
300      setFailure("master-delete-table", e);
301      return false;
302    }
303    return true;
304  }
305
306  private boolean preDelete(final MasterProcedureEnv env) throws IOException, InterruptedException {
307    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
308    if (cpHost != null) {
309      final TableName tableName = this.tableName;
310      cpHost.preDeleteTableAction(tableName, getUser());
311    }
312    return true;
313  }
314
315  private void postDelete(final MasterProcedureEnv env) throws IOException, InterruptedException {
316    deleteTableStates(env, tableName);
317
318    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
319    if (cpHost != null) {
320      final TableName tableName = this.tableName;
321      cpHost.postCompletedDeleteTableAction(tableName, getUser());
322    }
323  }
324
325  protected static void deleteFromFs(final MasterProcedureEnv env, final TableName tableName,
326    final List<RegionInfo> regions, final boolean archive) throws IOException {
327    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
328    final FileSystem fs = mfs.getFileSystem();
329    final Configuration conf = env.getMasterConfiguration();
330
331    final Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), tableName);
332
333    if (fs.exists(tableDir)) {
334      // Archive regions from FS (temp directory)
335      if (archive) {
336        List<Path> regionDirList = new ArrayList<>();
337        for (RegionInfo region : regions) {
338          if (RegionReplicaUtil.isDefaultReplica(region)) {
339            regionDirList.add(FSUtils.getRegionDirFromTableDir(tableDir, region));
340            List<RegionInfo> mergeRegions =
341              env.getAssignmentManager().getRegionStateStore().getMergeRegions(region);
342            if (!CollectionUtils.isEmpty(mergeRegions)) {
343              mergeRegions.stream()
344                .forEach(r -> regionDirList.add(FSUtils.getRegionDirFromTableDir(tableDir, r)));
345            }
346          }
347        }
348        HFileArchiver.archiveRegions(conf, fs, mfs.getRootDir(), tableDir, regionDirList);
349        if (!regionDirList.isEmpty()) {
350          LOG.debug("Archived {} regions", tableName);
351        }
352      }
353
354      // Archive mob data
355      Path mobTableDir =
356        CommonFSUtils.getTableDir(new Path(mfs.getRootDir(), MobConstants.MOB_DIR_NAME), tableName);
357      Path regionDir = new Path(mobTableDir, MobUtils.getMobRegionInfo(tableName).getEncodedName());
358      if (fs.exists(regionDir)) {
359        HFileArchiver.archiveRegion(conf, fs, mfs.getRootDir(), mobTableDir, regionDir);
360      }
361
362      // Delete table directory from FS
363      if (!fs.delete(tableDir, true) && fs.exists(tableDir)) {
364        throw new IOException("Couldn't delete " + tableDir);
365      }
366
367      // Delete the table directory where the mob files are saved
368      if (mobTableDir != null && fs.exists(mobTableDir)) {
369        if (!fs.delete(mobTableDir, true)) {
370          throw new IOException("Couldn't delete mob dir " + mobTableDir);
371        }
372      }
373
374      // Delete the directory on wal filesystem
375      FileSystem walFs = mfs.getWALFileSystem();
376      Path tableWALDir = CommonFSUtils.getWALTableDir(env.getMasterConfiguration(), tableName);
377      if (walFs.exists(tableWALDir) && !walFs.delete(tableWALDir, true)) {
378        throw new IOException("Couldn't delete table dir on wal filesystem" + tableWALDir);
379      }
380    }
381  }
382
383  /**
384   * There may be items for this table still up in hbase:meta in the case where the info:regioninfo
385   * column was empty because of some write error. Remove ALL rows from hbase:meta that have to do
386   * with this table.
387   * <p/>
388   * See HBASE-12980.
389   */
390  private static void cleanRegionsInMeta(final MasterProcedureEnv env, final TableName tableName)
391    throws IOException {
392    Scan tableScan = MetaTableAccessor.getScanForTableName(env.getMasterConfiguration(), tableName)
393      .setFilter(new KeyOnlyFilter());
394    long now = EnvironmentEdgeManager.currentTime();
395    List<Delete> deletes = new ArrayList<>();
396    try (
397      Table metaTable = env.getMasterServices().getConnection().getTable(TableName.META_TABLE_NAME);
398      ResultScanner scanner = metaTable.getScanner(tableScan)) {
399      for (;;) {
400        Result result = scanner.next();
401        if (result == null) {
402          break;
403        }
404        deletes.add(new Delete(result.getRow(), now));
405      }
406      if (!deletes.isEmpty()) {
407        LOG.warn("Deleting some vestigial " + deletes.size() + " rows of " + tableName + " from "
408          + TableName.META_TABLE_NAME);
409        metaTable.delete(deletes);
410      }
411    }
412  }
413
414  protected static void deleteFromMeta(final MasterProcedureEnv env, final TableName tableName,
415    List<RegionInfo> regions) throws IOException {
416    // Clean any remaining rows for this table.
417    cleanRegionsInMeta(env, tableName);
418
419    // clean region references from the server manager
420    env.getMasterServices().getServerManager().removeRegions(regions);
421
422    // Clear Favored Nodes for this table
423    FavoredNodesManager fnm = env.getMasterServices().getFavoredNodesManager();
424    if (fnm != null) {
425      fnm.deleteFavoredNodesForRegions(regions);
426    }
427
428    deleteTableDescriptorCache(env, tableName);
429  }
430
431  protected static void deleteAssignmentState(final MasterProcedureEnv env,
432    final TableName tableName) throws IOException {
433    // Clean up regions of the table in RegionStates.
434    LOG.debug("Removing '" + tableName + "' from region states.");
435    env.getMasterServices().getAssignmentManager().deleteTable(tableName);
436
437    // If entry for this table states, remove it.
438    LOG.debug("Marking '" + tableName + "' as deleted.");
439    env.getMasterServices().getTableStateManager().setDeletedTable(tableName);
440  }
441
442  protected static void deleteTableDescriptorCache(final MasterProcedureEnv env,
443    final TableName tableName) throws IOException {
444    LOG.debug("Removing '" + tableName + "' descriptor.");
445    env.getMasterServices().getTableDescriptors().remove(tableName);
446  }
447
448  protected static void deleteTableStates(final MasterProcedureEnv env, final TableName tableName)
449    throws IOException {
450    if (!tableName.isSystemTable()) {
451      ProcedureSyncWait.getMasterQuotaManager(env).removeTableFromNamespaceQuota(tableName);
452    }
453  }
454}