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}