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 createRegionOnFileSystem(env); 113 addChildProcedure(createAssignProcedures(env)); 114 setNextState(TruncateRegionState.TRUNCATE_REGION_POST_OPERATION); 115 break; 116 case TRUNCATE_REGION_POST_OPERATION: 117 postTruncate(env); 118 LOG.debug("truncate '" + getTableName() + "' completed"); 119 return Flow.NO_MORE_STATE; 120 default: 121 throw new UnsupportedOperationException("unhandled state=" + state); 122 } 123 } catch (IOException e) { 124 if (isRollbackSupported(state)) { 125 setFailure("master-truncate-region", e); 126 } else { 127 LOG.warn("Retriable error trying to truncate region=" + getRegion().getRegionNameAsString() 128 + " state=" + state, e); 129 } 130 } 131 return Flow.HAS_MORE_STATE; 132 } 133 134 private void createRegionOnFileSystem(final MasterProcedureEnv env) throws IOException { 135 RegionStateNode regionNode = 136 env.getAssignmentManager().getRegionStates().getRegionStateNode(getRegion()); 137 regionNode.lock(); 138 try { 139 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 140 final Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), getTableName()); 141 HRegionFileSystem.createRegionOnFileSystem(env.getMasterConfiguration(), mfs.getFileSystem(), 142 tableDir, getRegion()); 143 } finally { 144 regionNode.unlock(); 145 } 146 } 147 148 private void deleteRegionFromFileSystem(final MasterProcedureEnv env) throws IOException { 149 RegionStateNode regionNode = 150 env.getAssignmentManager().getRegionStates().getRegionStateNode(getRegion()); 151 regionNode.lock(); 152 try { 153 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem(); 154 final Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), getTableName()); 155 HRegionFileSystem.deleteRegionFromFileSystem(env.getMasterConfiguration(), 156 mfs.getFileSystem(), tableDir, getRegion()); 157 } finally { 158 regionNode.unlock(); 159 } 160 } 161 162 @Override 163 protected void rollbackState(final MasterProcedureEnv env, final TruncateRegionState state) 164 throws IOException { 165 switch (state) { 166 case TRUNCATE_REGION_PRE_OPERATION: 167 // Nothing to rollback, pre-truncate is just table-state checks. 168 return; 169 case TRUNCATE_REGION_SNAPSHOT: 170 // Handle recovery snapshot rollback. There is no DeleteSnapshotProcedure as such to use 171 // here directly as a child procedure, so we call a utility method to delete the snapshot 172 // which uses the SnapshotManager to delete the snapshot. 173 if (recoverySnapshotName != null) { 174 RecoverySnapshotUtils.deleteRecoverySnapshot(env, recoverySnapshotName, getTableName()); 175 recoverySnapshotName = null; 176 } 177 return; 178 case TRUNCATE_REGION_MAKE_OFFLINE: 179 RegionStateNode regionNode = 180 env.getAssignmentManager().getRegionStates().getRegionStateNode(getRegion()); 181 if (regionNode == null) { 182 // Region was unassigned by state TRUNCATE_REGION_MAKE_OFFLINE. 183 // So Assign it back 184 addChildProcedure(createAssignProcedures(env)); 185 } 186 return; 187 default: 188 // The truncate doesn't have a rollback. The execution will succeed, at some point. 189 throw new UnsupportedOperationException("unhandled state=" + state); 190 } 191 } 192 193 @Override 194 protected void completionCleanup(final MasterProcedureEnv env) { 195 releaseSyncLatch(); 196 } 197 198 @Override 199 protected boolean isRollbackSupported(final TruncateRegionState state) { 200 switch (state) { 201 case TRUNCATE_REGION_PRE_OPERATION: 202 case TRUNCATE_REGION_SNAPSHOT: 203 case TRUNCATE_REGION_MAKE_OFFLINE: 204 return true; 205 default: 206 return false; 207 } 208 } 209 210 @Override 211 protected TruncateRegionState getState(final int stateId) { 212 return TruncateRegionState.forNumber(stateId); 213 } 214 215 @Override 216 protected int getStateId(final TruncateRegionState state) { 217 return state.getNumber(); 218 } 219 220 @Override 221 protected TruncateRegionState getInitialState() { 222 return TruncateRegionState.TRUNCATE_REGION_PRE_OPERATION; 223 } 224 225 @Override 226 public void toStringClassDetails(StringBuilder sb) { 227 sb.append(getClass().getSimpleName()); 228 sb.append(" (region="); 229 sb.append(getRegion().getRegionNameAsString()); 230 sb.append(")"); 231 } 232 233 private boolean prepareTruncate() throws IOException { 234 if (getTableName().equals(TableName.META_TABLE_NAME)) { 235 throw new IOException("Can't truncate region in catalog tables"); 236 } 237 return true; 238 } 239 240 private void preTruncate(final MasterProcedureEnv env) throws IOException { 241 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 242 if (cpHost != null) { 243 cpHost.preTruncateRegionAction(getRegion(), getUser()); 244 } 245 } 246 247 private void postTruncate(final MasterProcedureEnv env) throws IOException { 248 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 249 if (cpHost != null) { 250 cpHost.postTruncateRegionAction(getRegion(), getUser()); 251 } 252 } 253 254 @Override 255 public TableOperationType getTableOperationType() { 256 return TableOperationType.REGION_TRUNCATE; 257 } 258 259 @Override 260 protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException { 261 super.serializeStateData(serializer); 262 TruncateRegionStateData.Builder state = TruncateRegionStateData.newBuilder() 263 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 264 .setRegionInfo(ProtobufUtil.toRegionInfo(getRegion())); 265 if (recoverySnapshotName != null) { 266 state.setSnapshotName(recoverySnapshotName); 267 } 268 serializer.serialize(state.build()); 269 } 270 271 @Override 272 protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException { 273 super.deserializeStateData(serializer); 274 TruncateRegionStateData state = serializer.deserialize(TruncateRegionStateData.class); 275 setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo())); 276 setRegion(ProtobufUtil.toRegionInfo(state.getRegionInfo())); 277 if (state.hasSnapshotName()) { 278 recoverySnapshotName = state.getSnapshotName(); 279 } 280 } 281 282 private TransitRegionStateProcedure createUnAssignProcedures(MasterProcedureEnv env) 283 throws IOException { 284 return env.getAssignmentManager().createOneUnassignProcedure(getRegion(), true, true); 285 } 286 287 private TransitRegionStateProcedure createAssignProcedures(MasterProcedureEnv env) { 288 return env.getAssignmentManager().createOneAssignProcedure(getRegion(), true, true); 289 } 290 291 @Override 292 protected boolean holdLock(MasterProcedureEnv env) { 293 if (RecoverySnapshotUtils.isRecoveryEnabled(env)) { 294 // If we are to take a recovery snapshot before deleting the region we will need to allow the 295 // snapshot procedure to lock the table. 296 return false; 297 } 298 return super.holdLock(env); 299 } 300}