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