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.regionserver.HRegionFileSystem;
030import org.apache.hadoop.hbase.util.CommonFSUtils;
031import org.apache.yetus.audience.InterfaceAudience;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.TruncateRegionState;
036
037@InterfaceAudience.Private
038public class TruncateRegionProcedure
039  extends AbstractStateMachineRegionProcedure<TruncateRegionState> {
040  private static final Logger LOG = LoggerFactory.getLogger(TruncateRegionProcedure.class);
041
042  @SuppressWarnings("unused")
043  public TruncateRegionProcedure() {
044    // Required by the Procedure framework to create the procedure on replay
045    super();
046  }
047
048  public TruncateRegionProcedure(final MasterProcedureEnv env, final RegionInfo hri)
049    throws HBaseIOException {
050    super(env, hri);
051    checkOnline(env, getRegion());
052  }
053
054  public TruncateRegionProcedure(final MasterProcedureEnv env, final RegionInfo region,
055    ProcedurePrepareLatch latch) throws HBaseIOException {
056    super(env, region, latch);
057    preflightChecks(env, true);
058  }
059
060  @Override
061  protected Flow executeFromState(final MasterProcedureEnv env, TruncateRegionState state)
062    throws InterruptedException {
063    if (LOG.isTraceEnabled()) {
064      LOG.trace(this + " execute state=" + state);
065    }
066    try {
067      switch (state) {
068        case TRUNCATE_REGION_PRE_OPERATION:
069          if (!prepareTruncate()) {
070            assert isFailed() : "the truncate should have an exception here";
071            return Flow.NO_MORE_STATE;
072          }
073          checkOnline(env, getRegion());
074          assert getRegion().getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID || isFailed()
075            : "Can't truncate replicas directly. "
076              + "Replicas are auto-truncated when their primary is truncated.";
077          preTruncate(env);
078          setNextState(TruncateRegionState.TRUNCATE_REGION_MAKE_OFFLINE);
079          break;
080        case TRUNCATE_REGION_MAKE_OFFLINE:
081          addChildProcedure(createUnAssignProcedures(env));
082          setNextState(TruncateRegionState.TRUNCATE_REGION_REMOVE);
083          break;
084        case TRUNCATE_REGION_REMOVE:
085          deleteRegionFromFileSystem(env);
086          setNextState(TruncateRegionState.TRUNCATE_REGION_MAKE_ONLINE);
087          break;
088        case TRUNCATE_REGION_MAKE_ONLINE:
089          addChildProcedure(createAssignProcedures(env));
090          setNextState(TruncateRegionState.TRUNCATE_REGION_POST_OPERATION);
091          break;
092        case TRUNCATE_REGION_POST_OPERATION:
093          postTruncate(env);
094          LOG.debug("truncate '" + getTableName() + "' completed");
095          return Flow.NO_MORE_STATE;
096        default:
097          throw new UnsupportedOperationException("unhandled state=" + state);
098      }
099    } catch (IOException e) {
100      if (isRollbackSupported(state)) {
101        setFailure("master-truncate-region", e);
102      } else {
103        LOG.warn("Retriable error trying to truncate region=" + getRegion().getRegionNameAsString()
104          + " state=" + state, e);
105      }
106    }
107    return Flow.HAS_MORE_STATE;
108  }
109
110  private void deleteRegionFromFileSystem(final MasterProcedureEnv env) throws IOException {
111    RegionStateNode regionNode =
112      env.getAssignmentManager().getRegionStates().getRegionStateNode(getRegion());
113    regionNode.lock();
114    try {
115      final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
116      final Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), getTableName());
117      HRegionFileSystem.deleteRegionFromFileSystem(env.getMasterConfiguration(),
118        mfs.getFileSystem(), tableDir, getRegion());
119    } finally {
120      regionNode.unlock();
121    }
122  }
123
124  @Override
125  protected void rollbackState(final MasterProcedureEnv env, final TruncateRegionState state)
126    throws IOException {
127    if (state == TruncateRegionState.TRUNCATE_REGION_PRE_OPERATION) {
128      // Nothing to rollback, pre-truncate is just table-state checks.
129      return;
130    }
131    if (state == TruncateRegionState.TRUNCATE_REGION_MAKE_OFFLINE) {
132      RegionStateNode regionNode =
133        env.getAssignmentManager().getRegionStates().getRegionStateNode(getRegion());
134      if (regionNode == null) {
135        // Region was unassigned by state TRUNCATE_REGION_MAKE_OFFLINE.
136        // So Assign it back
137        addChildProcedure(createAssignProcedures(env));
138      }
139      return;
140    }
141    // The truncate doesn't have a rollback. The execution will succeed, at some point.
142    throw new UnsupportedOperationException("unhandled state=" + state);
143  }
144
145  @Override
146  protected void completionCleanup(final MasterProcedureEnv env) {
147    releaseSyncLatch();
148  }
149
150  @Override
151  protected boolean isRollbackSupported(final TruncateRegionState state) {
152    switch (state) {
153      case TRUNCATE_REGION_PRE_OPERATION:
154        return true;
155      case TRUNCATE_REGION_MAKE_OFFLINE:
156        return true;
157      default:
158        return false;
159    }
160  }
161
162  @Override
163  protected TruncateRegionState getState(final int stateId) {
164    return TruncateRegionState.forNumber(stateId);
165  }
166
167  @Override
168  protected int getStateId(final TruncateRegionState state) {
169    return state.getNumber();
170  }
171
172  @Override
173  protected TruncateRegionState getInitialState() {
174    return TruncateRegionState.TRUNCATE_REGION_PRE_OPERATION;
175  }
176
177  @Override
178  public void toStringClassDetails(StringBuilder sb) {
179    sb.append(getClass().getSimpleName());
180    sb.append(" (region=");
181    sb.append(getRegion().getRegionNameAsString());
182    sb.append(")");
183  }
184
185  private boolean prepareTruncate() throws IOException {
186    if (getTableName().equals(TableName.META_TABLE_NAME)) {
187      throw new IOException("Can't truncate region in catalog tables");
188    }
189    return true;
190  }
191
192  private void preTruncate(final MasterProcedureEnv env) throws IOException {
193    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
194    if (cpHost != null) {
195      cpHost.preTruncateRegionAction(getRegion(), getUser());
196    }
197  }
198
199  private void postTruncate(final MasterProcedureEnv env) throws IOException {
200    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
201    if (cpHost != null) {
202      cpHost.postTruncateRegionAction(getRegion(), getUser());
203    }
204  }
205
206  @Override
207  public TableOperationType getTableOperationType() {
208    return TableOperationType.REGION_TRUNCATE;
209  }
210
211  private TransitRegionStateProcedure createUnAssignProcedures(MasterProcedureEnv env)
212    throws IOException {
213    return env.getAssignmentManager().createOneUnassignProcedure(getRegion(), true, true);
214  }
215
216  private TransitRegionStateProcedure createAssignProcedures(MasterProcedureEnv env) {
217    return env.getAssignmentManager().createOneAssignProcedure(getRegion(), true, true);
218  }
219}