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}