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 = MetaTableAccessor 283 .getMergeRegions(env.getMasterServices().getConnection(), region.getRegionName()); 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}