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