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