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