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