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.assignment;
019
020import edu.umd.cs.findbugs.annotations.Nullable;
021import java.io.IOException;
022import org.apache.hadoop.hbase.HBaseIOException;
023import org.apache.hadoop.hbase.ServerName;
024import org.apache.hadoop.hbase.TableName;
025import org.apache.hadoop.hbase.client.RegionInfo;
026import org.apache.hadoop.hbase.client.RegionReplicaUtil;
027import org.apache.hadoop.hbase.client.RetriesExhaustedException;
028import org.apache.hadoop.hbase.master.MetricsAssignmentManager;
029import org.apache.hadoop.hbase.master.RegionState.State;
030import org.apache.hadoop.hbase.master.procedure.AbstractStateMachineRegionProcedure;
031import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
032import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
033import org.apache.hadoop.hbase.procedure2.Procedure;
034import org.apache.hadoop.hbase.procedure2.ProcedureMetrics;
035import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
036import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException;
037import org.apache.hadoop.hbase.procedure2.ProcedureUtil;
038import org.apache.hadoop.hbase.procedure2.ProcedureYieldException;
039import org.apache.hadoop.hbase.util.RetryCounter;
040import org.apache.yetus.audience.InterfaceAudience;
041import org.slf4j.Logger;
042import org.slf4j.LoggerFactory;
043
044import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
045
046import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
047import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RegionStateTransitionState;
048import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RegionStateTransitionStateData;
049import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RegionTransitionType;
050import org.apache.hadoop.hbase.shaded.protobuf.generated.ProcedureProtos;
051import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionStateTransition.TransitionCode;
052
053/**
054 * The procedure to deal with the state transition of a region. A region with a TRSP in place is
055 * called RIT, i.e, RegionInTransition.
056 * <p/>
057 * It can be used to assign/unassign/reopen/move a region, and for
058 * {@link #unassign(MasterProcedureEnv, RegionInfo)} and
059 * {@link #reopen(MasterProcedureEnv, RegionInfo)}, you do not need to specify a target server, and
060 * for {@link #assign(MasterProcedureEnv, RegionInfo, ServerName)} and
061 * {@link #move(MasterProcedureEnv, RegionInfo, ServerName)}, if you want to you can provide a
062 * target server. And for {@link #move(MasterProcedureEnv, RegionInfo, ServerName)}, if you do not
063 * specify a targetServer, we will select one randomly.
064 * <p/>
065 * <p/>
066 * The typical state transition for assigning a region is:
067 *
068 * <pre>
069 * GET_ASSIGN_CANDIDATE ------> OPEN -----> CONFIRM_OPENED
070 * </pre>
071 *
072 * Notice that, if there are failures we may go back to the {@code GET_ASSIGN_CANDIDATE} state to
073 * try again.
074 * <p/>
075 * The typical state transition for unassigning a region is:
076 *
077 * <pre>
078 * CLOSE -----> CONFIRM_CLOSED
079 * </pre>
080 *
081 * Here things go a bit different, if there are failures, especially that if there is a server
082 * crash, we will go to the {@code GET_ASSIGN_CANDIDATE} state to bring the region online first, and
083 * then go through the normal way to unassign it.
084 * <p/>
085 * The typical state transition for reopening/moving a region is:
086 *
087 * <pre>
088 * CLOSE -----> CONFIRM_CLOSED -----> GET_ASSIGN_CANDIDATE ------> OPEN -----> CONFIRM_OPENED
089 * </pre>
090 *
091 * The retry logic is the same with the above assign/unassign.
092 * <p/>
093 * Notice that, although we allow specify a target server, it just acts as a candidate, we do not
094 * guarantee that the region will finally be on the target server. If this is important for you, you
095 * should check whether the region is on the target server after the procedure is finished.
096 * <p/>
097 * When you want to schedule a TRSP, please check whether there is still one for this region, and
098 * the check should be under the RegionStateNode lock. We will remove the TRSP from a
099 * RegionStateNode when we are done, see the code in {@code reportTransition} method below. There
100 * could be at most one TRSP for a give region.
101 */
102@InterfaceAudience.Private
103public class TransitRegionStateProcedure
104    extends AbstractStateMachineRegionProcedure<RegionStateTransitionState> {
105
106  private static final Logger LOG = LoggerFactory.getLogger(TransitRegionStateProcedure.class);
107
108  private TransitionType type;
109
110  private RegionStateTransitionState initialState;
111
112  private RegionStateTransitionState lastState;
113
114  // the candidate where we want to assign the region to.
115  private ServerName assignCandidate;
116
117  private boolean forceNewPlan;
118
119  private RetryCounter retryCounter;
120
121  private RegionRemoteProcedureBase remoteProc;
122
123  public TransitRegionStateProcedure() {
124  }
125
126  private void setInitalAndLastState() {
127    switch (type) {
128      case ASSIGN:
129        initialState = RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE;
130        lastState = RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_OPENED;
131        break;
132      case UNASSIGN:
133        initialState = RegionStateTransitionState.REGION_STATE_TRANSITION_CLOSE;
134        lastState = RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_CLOSED;
135        break;
136      case MOVE:
137      case REOPEN:
138        initialState = RegionStateTransitionState.REGION_STATE_TRANSITION_CLOSE;
139        lastState = RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_OPENED;
140        break;
141      default:
142        throw new IllegalArgumentException("Unknown TransitionType: " + type);
143    }
144  }
145
146  @VisibleForTesting
147  protected TransitRegionStateProcedure(MasterProcedureEnv env, RegionInfo hri,
148      ServerName assignCandidate, boolean forceNewPlan, TransitionType type) {
149    super(env, hri);
150    this.assignCandidate = assignCandidate;
151    this.forceNewPlan = forceNewPlan;
152    this.type = type;
153    setInitalAndLastState();
154
155    // when do reopen TRSP, let the rs know the targetServer so it can keep some info on close
156    if (type == TransitionType.REOPEN) {
157      this.assignCandidate = getRegionStateNode(env).getRegionLocation();
158    }
159  }
160
161  @Override
162  public TableOperationType getTableOperationType() {
163    // TODO: maybe we should make another type here, REGION_TRANSITION?
164    return TableOperationType.REGION_EDIT;
165  }
166
167  @Override
168  protected boolean waitInitialized(MasterProcedureEnv env) {
169    if (TableName.isMetaTableName(getTableName())) {
170      return false;
171    }
172    // First we need meta to be loaded, and second, if meta is not online then we will likely to
173    // fail when updating meta so we wait until it is assigned.
174    AssignmentManager am = env.getAssignmentManager();
175    return am.waitMetaLoaded(this) || am.waitMetaAssigned(this, getRegion());
176  }
177
178  private void queueAssign(MasterProcedureEnv env, RegionStateNode regionNode)
179      throws ProcedureSuspendedException {
180    boolean retain = false;
181    if (forceNewPlan) {
182      // set the region location to null if forceNewPlan is true
183      regionNode.setRegionLocation(null);
184    } else {
185      if (assignCandidate != null) {
186        retain = assignCandidate.equals(regionNode.getLastHost());
187        regionNode.setRegionLocation(assignCandidate);
188      } else if (regionNode.getLastHost() != null) {
189        retain = true;
190        LOG.info("Setting lastHost as the region location {}", regionNode.getLastHost());
191        regionNode.setRegionLocation(regionNode.getLastHost());
192      }
193    }
194    LOG.info("Starting {}; {}; forceNewPlan={}, retain={}", this, regionNode.toShortString(),
195      forceNewPlan, retain);
196    env.getAssignmentManager().queueAssign(regionNode);
197    setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_OPEN);
198    if (regionNode.getProcedureEvent().suspendIfNotReady(this)) {
199      throw new ProcedureSuspendedException();
200    }
201  }
202
203  private void openRegion(MasterProcedureEnv env, RegionStateNode regionNode) throws IOException {
204    ServerName loc = regionNode.getRegionLocation();
205    if (loc == null) {
206      LOG.warn("No location specified for {}, jump back to state {} to get one", getRegion(),
207        RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE);
208      setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE);
209      return;
210    }
211    env.getAssignmentManager().regionOpening(regionNode);
212    addChildProcedure(new OpenRegionProcedure(this, getRegion(), loc));
213    setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_OPENED);
214  }
215
216  private Flow confirmOpened(MasterProcedureEnv env, RegionStateNode regionNode)
217      throws IOException {
218    if (regionNode.isInState(State.OPEN)) {
219      retryCounter = null;
220      if (lastState == RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_OPENED) {
221        // we are the last state, finish
222        regionNode.unsetProcedure(this);
223        ServerCrashProcedure.updateProgress(env, getParentProcId());
224        return Flow.NO_MORE_STATE;
225      }
226      // It is possible that we arrive here but confirm opened is not the last state, for example,
227      // when merging or splitting a region, we unassign the region from a RS and the RS is crashed,
228      // then there will be recovered edits for this region, we'd better make the region online
229      // again and then unassign it, otherwise we have to fail the merge/split procedure as we may
230      // loss data.
231      setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_CLOSE);
232      return Flow.HAS_MORE_STATE;
233    }
234
235    int retries = env.getAssignmentManager().getRegionStates().addToFailedOpen(regionNode)
236        .incrementAndGetRetries();
237    int maxAttempts = env.getAssignmentManager().getAssignMaxAttempts();
238    LOG.info("Retry={} of max={}; {}; {}", retries, maxAttempts, this, regionNode.toShortString());
239
240    if (retries >= maxAttempts) {
241      env.getAssignmentManager().regionFailedOpen(regionNode, true);
242      setFailure(getClass().getSimpleName(), new RetriesExhaustedException(
243        "Max attempts " + env.getAssignmentManager().getAssignMaxAttempts() + " exceeded"));
244      regionNode.unsetProcedure(this);
245      return Flow.NO_MORE_STATE;
246    }
247
248    env.getAssignmentManager().regionFailedOpen(regionNode, false);
249    // we failed to assign the region, force a new plan
250    forceNewPlan = true;
251    regionNode.setRegionLocation(null);
252    setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE);
253
254    if (retries > env.getAssignmentManager().getAssignRetryImmediatelyMaxAttempts()) {
255      // Throw exception to backoff and retry when failed open too many times
256      throw new HBaseIOException("Failed confirm OPEN of " + regionNode +
257          " (remote log may yield more detail on why).");
258    } else {
259      // Here we do not throw exception because we want to the region to be online ASAP
260      return Flow.HAS_MORE_STATE;
261    }
262  }
263
264  private void closeRegion(MasterProcedureEnv env, RegionStateNode regionNode) throws IOException {
265    if (regionNode.isInState(State.OPEN, State.CLOSING, State.MERGING, State.SPLITTING)) {
266      // this is the normal case
267      env.getAssignmentManager().regionClosing(regionNode);
268      addChildProcedure(new CloseRegionProcedure(this, getRegion(), regionNode.getRegionLocation(),
269        assignCandidate));
270      setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_CLOSED);
271    } else {
272      forceNewPlan = true;
273      regionNode.setRegionLocation(null);
274      setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE);
275    }
276  }
277
278  private Flow confirmClosed(MasterProcedureEnv env, RegionStateNode regionNode)
279      throws IOException {
280    if (regionNode.isInState(State.CLOSED)) {
281      retryCounter = null;
282      if (lastState == RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_CLOSED) {
283        // we are the last state, finish
284        regionNode.unsetProcedure(this);
285        return Flow.NO_MORE_STATE;
286      }
287      // This means we need to open the region again, should be a move or reopen
288      setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE);
289      return Flow.HAS_MORE_STATE;
290    }
291    if (regionNode.isInState(State.CLOSING)) {
292      // This is possible, think the target RS crashes and restarts immediately, the close region
293      // operation will return a NotServingRegionException soon, we can only recover after SCP takes
294      // care of this RS. So here we throw an IOException to let upper layer to retry with backoff.
295      setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_CLOSE);
296      throw new HBaseIOException("Failed to close region");
297    }
298    // abnormally closed, need to reopen it, no matter what is the last state, see the comment in
299    // confirmOpened for more details that why we need to reopen the region first even if we just
300    // want to close it.
301    // The only exception is for non-default replica, where we do not need to deal with recovered
302    // edits. Notice that the region will remain in ABNORMALLY_CLOSED state, the upper layer need to
303    // deal with this state. For non-default replica, this is usually the same with CLOSED.
304    assert regionNode.isInState(State.ABNORMALLY_CLOSED);
305    if (!RegionReplicaUtil.isDefaultReplica(getRegion()) &&
306      lastState == RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_CLOSED) {
307      regionNode.unsetProcedure(this);
308      return Flow.NO_MORE_STATE;
309    }
310    retryCounter = null;
311    setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE);
312    return Flow.HAS_MORE_STATE;
313  }
314
315  // Override to lock RegionStateNode
316  @SuppressWarnings("rawtypes")
317  @Override
318  protected Procedure[] execute(MasterProcedureEnv env)
319      throws ProcedureSuspendedException, ProcedureYieldException, InterruptedException {
320    RegionStateNode regionNode =
321      env.getAssignmentManager().getRegionStates().getOrCreateRegionStateNode(getRegion());
322    regionNode.lock();
323    try {
324      return super.execute(env);
325    } finally {
326      regionNode.unlock();
327    }
328  }
329
330  private RegionStateNode getRegionStateNode(MasterProcedureEnv env) {
331    return env.getAssignmentManager().getRegionStates().getOrCreateRegionStateNode(getRegion());
332  }
333
334  @Override
335  protected Flow executeFromState(MasterProcedureEnv env, RegionStateTransitionState state)
336      throws ProcedureSuspendedException, ProcedureYieldException, InterruptedException {
337    RegionStateNode regionNode = getRegionStateNode(env);
338    try {
339      switch (state) {
340        case REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE:
341          // Need to do some sanity check for replica region, if the region does not exist at
342          // master, do not try to assign the replica region, log error and return.
343          if (!RegionReplicaUtil.isDefaultReplica(regionNode.getRegionInfo())) {
344            RegionInfo defaultRI =
345              RegionReplicaUtil.getRegionInfoForDefaultReplica(regionNode.getRegionInfo());
346            if (env.getMasterServices().getAssignmentManager().getRegionStates().
347              getRegionStateNode(defaultRI) == null) {
348              LOG.error(
349                "Cannot assign replica region {} because its primary region {} does not exist.",
350                regionNode.getRegionInfo(), defaultRI);
351              return Flow.NO_MORE_STATE;
352            }
353          }
354          queueAssign(env, regionNode);
355          return Flow.HAS_MORE_STATE;
356        case REGION_STATE_TRANSITION_OPEN:
357          openRegion(env, regionNode);
358          return Flow.HAS_MORE_STATE;
359        case REGION_STATE_TRANSITION_CONFIRM_OPENED:
360          return confirmOpened(env, regionNode);
361        case REGION_STATE_TRANSITION_CLOSE:
362          closeRegion(env, regionNode);
363          return Flow.HAS_MORE_STATE;
364        case REGION_STATE_TRANSITION_CONFIRM_CLOSED:
365          return confirmClosed(env, regionNode);
366        default:
367          throw new UnsupportedOperationException("unhandled state=" + state);
368      }
369    } catch (IOException e) {
370      if (retryCounter == null) {
371        retryCounter = ProcedureUtil.createRetryCounter(env.getMasterConfiguration());
372      }
373      long backoff = retryCounter.getBackoffTimeAndIncrementAttempts();
374      LOG.warn(
375        "Failed transition, suspend {}secs {}; {}; waiting on rectified condition fixed " +
376          "by other Procedure or operator intervention",
377        backoff / 1000, this, regionNode.toShortString(), e);
378      setTimeout(Math.toIntExact(backoff));
379      setState(ProcedureProtos.ProcedureState.WAITING_TIMEOUT);
380      skipPersistence();
381      throw new ProcedureSuspendedException();
382    }
383  }
384
385  /**
386   * At end of timeout, wake ourselves up so we run again.
387   */
388  @Override
389  protected synchronized boolean setTimeoutFailure(MasterProcedureEnv env) {
390    setState(ProcedureProtos.ProcedureState.RUNNABLE);
391    env.getProcedureScheduler().addFront(this);
392    return false; // 'false' means that this procedure handled the timeout
393  }
394
395  // Should be called with RegionStateNode locked
396  public void reportTransition(MasterProcedureEnv env, RegionStateNode regionNode,
397      ServerName serverName, TransitionCode code, long seqId, long procId) throws IOException {
398    if (remoteProc == null) {
399      LOG.warn(
400        "There is no outstanding remote region procedure for {}, serverName={}, code={}," +
401          " seqId={}, proc={}, should be a retry, ignore",
402        regionNode, serverName, code, seqId, this);
403      return;
404    }
405    // The procId could be -1 if it is from an old region server, we need to deal with it so that we
406    // can do rolling upgraing.
407    if (procId >= 0 && remoteProc.getProcId() != procId) {
408      LOG.warn(
409        "The pid of remote region procedure for {} is {}, the reported pid={}, serverName={}," +
410          " code={}, seqId={}, proc={}, should be a retry, ignore",
411        regionNode, remoteProc.getProcId(), procId, serverName, code, seqId, this);
412      return;
413    }
414    remoteProc.reportTransition(env, regionNode, serverName, code, seqId);
415  }
416
417  // Should be called with RegionStateNode locked
418  public void serverCrashed(MasterProcedureEnv env, RegionStateNode regionNode,
419      ServerName serverName) throws IOException {
420    // force to assign to a new candidate server
421    // AssignmentManager#regionClosedAbnormally will set region location to null
422    // TODO: the forceNewPlan flag not be persistent so if master crash then the flag will be lost.
423    // But assign to old server is not big deal because it not effect correctness.
424    // See HBASE-23035 for more details.
425    forceNewPlan = true;
426    if (remoteProc != null) {
427      // this means we are waiting for the sub procedure, so wake it up
428      remoteProc.serverCrashed(env, regionNode, serverName);
429    } else {
430      // we are in RUNNING state, just update the region state, and we will process it later.
431      env.getAssignmentManager().regionClosedAbnormally(regionNode);
432    }
433  }
434
435  void attachRemoteProc(RegionRemoteProcedureBase proc) {
436    this.remoteProc = proc;
437  }
438
439  void unattachRemoteProc(RegionRemoteProcedureBase proc) {
440    assert this.remoteProc == proc;
441    this.remoteProc = null;
442  }
443
444  // will be called after we finish loading the meta entry for this region.
445  // used to change the state of the region node if we have a sub procedure, as we may not persist
446  // the state to meta yet. See the code in RegionRemoteProcedureBase.execute for more details.
447  void stateLoaded(AssignmentManager am, RegionStateNode regionNode) {
448    if (remoteProc != null) {
449      remoteProc.stateLoaded(am, regionNode);
450    }
451  }
452
453  @Override
454  protected void rollbackState(MasterProcedureEnv env, RegionStateTransitionState state)
455      throws IOException, InterruptedException {
456    // no rollback
457    throw new UnsupportedOperationException();
458  }
459
460  @Override
461  protected RegionStateTransitionState getState(int stateId) {
462    return RegionStateTransitionState.forNumber(stateId);
463  }
464
465  @Override
466  protected int getStateId(RegionStateTransitionState state) {
467    return state.getNumber();
468  }
469
470  @Override
471  protected RegionStateTransitionState getInitialState() {
472    return initialState;
473  }
474
475  private static TransitionType convert(RegionTransitionType type) {
476    switch (type) {
477      case ASSIGN:
478        return TransitionType.ASSIGN;
479      case UNASSIGN:
480        return TransitionType.UNASSIGN;
481      case MOVE:
482        return TransitionType.MOVE;
483      case REOPEN:
484        return TransitionType.REOPEN;
485      default:
486        throw new IllegalArgumentException("Unknown RegionTransitionType: " + type);
487    }
488  }
489
490  private static RegionTransitionType convert(TransitionType type) {
491    switch (type) {
492      case ASSIGN:
493        return RegionTransitionType.ASSIGN;
494      case UNASSIGN:
495        return RegionTransitionType.UNASSIGN;
496      case MOVE:
497        return RegionTransitionType.MOVE;
498      case REOPEN:
499        return RegionTransitionType.REOPEN;
500      default:
501        throw new IllegalArgumentException("Unknown TransitionType: " + type);
502    }
503  }
504
505  @Override
506  protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException {
507    super.serializeStateData(serializer);
508    RegionStateTransitionStateData.Builder builder = RegionStateTransitionStateData.newBuilder()
509      .setType(convert(type)).setForceNewPlan(forceNewPlan);
510    if (assignCandidate != null) {
511      builder.setAssignCandidate(ProtobufUtil.toServerName(assignCandidate));
512    }
513    serializer.serialize(builder.build());
514  }
515
516  @Override
517  protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException {
518    super.deserializeStateData(serializer);
519    RegionStateTransitionStateData data =
520      serializer.deserialize(RegionStateTransitionStateData.class);
521    type = convert(data.getType());
522    setInitalAndLastState();
523    forceNewPlan = data.getForceNewPlan();
524    if (data.hasAssignCandidate()) {
525      assignCandidate = ProtobufUtil.toServerName(data.getAssignCandidate());
526    }
527  }
528
529  @Override
530  protected ProcedureMetrics getProcedureMetrics(MasterProcedureEnv env) {
531    MetricsAssignmentManager metrics = env.getAssignmentManager().getAssignmentManagerMetrics();
532    switch (type) {
533      case ASSIGN:
534        return metrics.getAssignProcMetrics();
535      case UNASSIGN:
536        return metrics.getUnassignProcMetrics();
537      case MOVE:
538        return metrics.getMoveProcMetrics();
539      case REOPEN:
540        return metrics.getReopenProcMetrics();
541      default:
542        throw new IllegalArgumentException("Unknown transition type: " + type);
543    }
544  }
545
546  @Override
547  public void toStringClassDetails(StringBuilder sb) {
548    super.toStringClassDetails(sb);
549    if (initialState == RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE) {
550      sb.append(", ASSIGN");
551    } else if (lastState == RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_CLOSED) {
552      sb.append(", UNASSIGN");
553    } else {
554      sb.append(", REOPEN/MOVE");
555    }
556  }
557
558  private static TransitRegionStateProcedure setOwner(MasterProcedureEnv env,
559      TransitRegionStateProcedure proc) {
560    proc.setOwner(env.getRequestUser().getShortName());
561    return proc;
562  }
563
564  public enum TransitionType {
565    ASSIGN, UNASSIGN, MOVE, REOPEN
566  }
567
568  // Be careful that, when you call these 4 methods below, you need to manually attach the returned
569  // procedure with the RegionStateNode, otherwise the procedure will quit immediately without doing
570  // anything. See the comment in executeFromState to find out why we need this assumption.
571  public static TransitRegionStateProcedure assign(MasterProcedureEnv env, RegionInfo region,
572      @Nullable ServerName targetServer) {
573    return assign(env, region, false, targetServer);
574  }
575
576  public static TransitRegionStateProcedure assign(MasterProcedureEnv env, RegionInfo region,
577      boolean forceNewPlan, @Nullable ServerName targetServer) {
578    return setOwner(env, new TransitRegionStateProcedure(env, region, targetServer, forceNewPlan,
579        TransitionType.ASSIGN));
580  }
581
582  public static TransitRegionStateProcedure unassign(MasterProcedureEnv env, RegionInfo region) {
583    return setOwner(env,
584      new TransitRegionStateProcedure(env, region, null, false, TransitionType.UNASSIGN));
585  }
586
587  public static TransitRegionStateProcedure reopen(MasterProcedureEnv env, RegionInfo region) {
588    return setOwner(env,
589      new TransitRegionStateProcedure(env, region, null, false, TransitionType.REOPEN));
590  }
591
592  public static TransitRegionStateProcedure move(MasterProcedureEnv env, RegionInfo region,
593      @Nullable ServerName targetServer) {
594    return setOwner(env, new TransitRegionStateProcedure(env, region, targetServer,
595      targetServer == null, TransitionType.MOVE));
596  }
597}