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 org.apache.hadoop.fs.Path; 022import org.apache.hadoop.hbase.HBaseIOException; 023import org.apache.hadoop.hbase.TableName; 024import org.apache.hadoop.hbase.client.RegionInfo; 025import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 026import org.apache.hadoop.hbase.master.MasterFileSystem; 027import org.apache.hadoop.hbase.master.assignment.RegionStateNode; 028import org.apache.hadoop.hbase.master.assignment.TransitRegionStateProcedure; 029import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 030import org.apache.hadoop.hbase.regionserver.HRegionFileSystem; 031import org.apache.hadoop.hbase.util.CommonFSUtils; 032import org.apache.yetus.audience.InterfaceAudience; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 037import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.TruncateRegionState; 038import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.TruncateRegionStateData; 039 040@InterfaceAudience.Private 041public class TruncateRegionProcedure 042 extends AbstractStateMachineRegionProcedure<TruncateRegionState> { 043 private static final Logger LOG = LoggerFactory.getLogger(TruncateRegionProcedure.class); 044 045 private String recoverySnapshotName; 046 047 @SuppressWarnings("unused") 048 public TruncateRegionProcedure() { 049 // Required by the Procedure framework to create the procedure on replay 050 super(); 051 } 052 053 public TruncateRegionProcedure(final MasterProcedureEnv env, final RegionInfo hri) 054 throws HBaseIOException { 055 super(env, hri); 056 checkOnline(env, getRegion()); 057 } 058 059 public TruncateRegionProcedure(final MasterProcedureEnv env, final RegionInfo region, 060 ProcedurePrepareLatch latch) throws HBaseIOException { 061 super(env, region, latch); 062 preflightChecks(env, true); 063 } 064 065 @Override 066 protected Flow executeFromState(final MasterProcedureEnv env, TruncateRegionState state) 067 throws InterruptedException { 068 if (LOG.isTraceEnabled()) { 069 LOG.trace(this + " execute state=" + state); 070 } 071 try { 072 switch (state) { 073 case TRUNCATE_REGION_PRE_OPERATION: 074 if (!prepareTruncate()) { 075 assert isFailed() : "the truncate should have an exception here"; 076 return Flow.NO_MORE_STATE; 077 } 078 checkOnline(env, getRegion()); 079 assert getRegion().getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID || isFailed() 080 : "Can't truncate replicas directly. " 081 + "Replicas are auto-truncated when their primary is truncated."; 082 preTruncate(env); 083 084 // Check if we should create a recovery snapshot 085 if (RecoverySnapshotUtils.isRecoveryEnabled(env)) { 086 setNextState(TruncateRegionState.TRUNCATE_REGION_SNAPSHOT); 087 } else { 088 setNextState(TruncateRegionState.TRUNCATE_REGION_MAKE_OFFLINE); 089 } 090 break; 091 case TRUNCATE_REGION_SNAPSHOT: 092 // Create recovery snapshot procedure as child procedure 093 recoverySnapshotName = RecoverySnapshotUtils.generateSnapshotName(getTableName()); 094 SnapshotProcedure snapshotProcedure = 095 RecoverySnapshotUtils.createSnapshotProcedure(env, getTableName(), recoverySnapshotName, 096 env.getMasterServices().getTableDescriptors().get(getTableName())); 097 // Submit snapshot procedure as child procedure 098 addChildProcedure(snapshotProcedure); 099 LOG.debug("Creating recovery snapshot {} for table {} before truncating region {}", 100 recoverySnapshotName, getTableName(), getRegion().getRegionNameAsString()); 101 setNextState(TruncateRegionState.TRUNCATE_REGION_MAKE_OFFLINE); 102 break; 103 case TRUNCATE_REGION_MAKE_OFFLINE: 104 addChildProcedure(createUnAssignProcedures(env)); 105 setNextState(TruncateRegionState.TRUNCATE_REGION_REMOVE); 106 break; 107 case TRUNCATE_REGION_REMOVE: 108 deleteRegionFromFileSystem(env); 109 setNextState(TruncateRegionState.TRUNCATE_REGION_MAKE_ONLINE); 110 break; 111 case TRUNCATE_REGION_MAKE_ONLINE: 112 addChildProcedure(createAssignProcedures(env)); 113 setNextState(TruncateRegionState.TRUNCATE_REGION_POST_OPERATION); 114 break; 115 case TRUNCATE_REGION_POST_OPERATION: 116 postTruncate(env); 117 LOG.debug("truncate '" + getTableName() + "' completed"); 118 return Flow.NO_MORE_STATE; 119 default: 120 throw new UnsupportedOperationException("unhandled state=" + state); 121 } 122 } catch (IOException e) { 123 if (isRollbackSupported(state)) { 124 setFailure("master-truncate-region", e); 125 } else { 126 LOG.warn("Retriable error trying to truncate region=" + getRegion().getRegionNameAsString() 127 + " state=" + state, e); 128 } 129 } 130 return Flow.HAS_MORE_STATE; 131 } 132 133 private void deleteRegionFromFileSystem(final MasterProcedureEnv env) throws IOException { 134 RegionStateNode regionNode = 135 env.getAssignmentManager().getRegionStates().getRegionStateNode(getRegion()); 136 regionNode.lock(); 137 try { 138 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 139 final Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), getTableName()); 140 HRegionFileSystem.deleteRegionFromFileSystem(env.getMasterConfiguration(), 141 mfs.getFileSystem(), tableDir, getRegion()); 142 } finally { 143 regionNode.unlock(); 144 } 145 } 146 147 @Override 148 protected void rollbackState(final MasterProcedureEnv env, final TruncateRegionState state) 149 throws IOException { 150 switch (state) { 151 case TRUNCATE_REGION_PRE_OPERATION: 152 // Nothing to rollback, pre-truncate is just table-state checks. 153 return; 154 case TRUNCATE_REGION_SNAPSHOT: 155 // Handle recovery snapshot rollback. There is no DeleteSnapshotProcedure as such to use 156 // here directly as a child procedure, so we call a utility method to delete the snapshot 157 // which uses the SnapshotManager to delete the snapshot. 158 if (recoverySnapshotName != null) { 159 RecoverySnapshotUtils.deleteRecoverySnapshot(env, recoverySnapshotName, getTableName()); 160 recoverySnapshotName = null; 161 } 162 return; 163 case TRUNCATE_REGION_MAKE_OFFLINE: 164 RegionStateNode regionNode = 165 env.getAssignmentManager().getRegionStates().getRegionStateNode(getRegion()); 166 if (regionNode == null) { 167 // Region was unassigned by state TRUNCATE_REGION_MAKE_OFFLINE. 168 // So Assign it back 169 addChildProcedure(createAssignProcedures(env)); 170 } 171 return; 172 default: 173 // The truncate doesn't have a rollback. The execution will succeed, at some point. 174 throw new UnsupportedOperationException("unhandled state=" + state); 175 } 176 } 177 178 @Override 179 protected void completionCleanup(final MasterProcedureEnv env) { 180 releaseSyncLatch(); 181 } 182 183 @Override 184 protected boolean isRollbackSupported(final TruncateRegionState state) { 185 switch (state) { 186 case TRUNCATE_REGION_PRE_OPERATION: 187 case TRUNCATE_REGION_SNAPSHOT: 188 case TRUNCATE_REGION_MAKE_OFFLINE: 189 return true; 190 default: 191 return false; 192 } 193 } 194 195 @Override 196 protected TruncateRegionState getState(final int stateId) { 197 return TruncateRegionState.forNumber(stateId); 198 } 199 200 @Override 201 protected int getStateId(final TruncateRegionState state) { 202 return state.getNumber(); 203 } 204 205 @Override 206 protected TruncateRegionState getInitialState() { 207 return TruncateRegionState.TRUNCATE_REGION_PRE_OPERATION; 208 } 209 210 @Override 211 public void toStringClassDetails(StringBuilder sb) { 212 sb.append(getClass().getSimpleName()); 213 sb.append(" (region="); 214 sb.append(getRegion().getRegionNameAsString()); 215 sb.append(")"); 216 } 217 218 private boolean prepareTruncate() throws IOException { 219 if (getTableName().equals(TableName.META_TABLE_NAME)) { 220 throw new IOException("Can't truncate region in catalog tables"); 221 } 222 return true; 223 } 224 225 private void preTruncate(final MasterProcedureEnv env) throws IOException { 226 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 227 if (cpHost != null) { 228 cpHost.preTruncateRegionAction(getRegion(), getUser()); 229 } 230 } 231 232 private void postTruncate(final MasterProcedureEnv env) throws IOException { 233 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 234 if (cpHost != null) { 235 cpHost.postTruncateRegionAction(getRegion(), getUser()); 236 } 237 } 238 239 @Override 240 public TableOperationType getTableOperationType() { 241 return TableOperationType.REGION_TRUNCATE; 242 } 243 244 @Override 245 protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException { 246 super.serializeStateData(serializer); 247 TruncateRegionStateData.Builder state = TruncateRegionStateData.newBuilder() 248 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 249 .setRegionInfo(ProtobufUtil.toRegionInfo(getRegion())); 250 if (recoverySnapshotName != null) { 251 state.setSnapshotName(recoverySnapshotName); 252 } 253 serializer.serialize(state.build()); 254 } 255 256 @Override 257 protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException { 258 super.deserializeStateData(serializer); 259 TruncateRegionStateData state = serializer.deserialize(TruncateRegionStateData.class); 260 setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo())); 261 setRegion(ProtobufUtil.toRegionInfo(state.getRegionInfo())); 262 if (state.hasSnapshotName()) { 263 recoverySnapshotName = state.getSnapshotName(); 264 } 265 } 266 267 private TransitRegionStateProcedure createUnAssignProcedures(MasterProcedureEnv env) 268 throws IOException { 269 return env.getAssignmentManager().createOneUnassignProcedure(getRegion(), true, true); 270 } 271 272 private TransitRegionStateProcedure createAssignProcedures(MasterProcedureEnv env) { 273 return env.getAssignmentManager().createOneAssignProcedure(getRegion(), true, true); 274 } 275 276 @Override 277 protected boolean holdLock(MasterProcedureEnv env) { 278 if (RecoverySnapshotUtils.isRecoveryEnabled(env)) { 279 // If we are to take a recovery snapshot before deleting the region we will need to allow the 280 // snapshot procedure to lock the table. 281 return false; 282 } 283 return super.holdLock(env); 284 } 285}