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 MetaTableAccessor.getMergeRegions(env.getMasterServices().getConnection(), 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}