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}