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