private Flow confirmOpened(MasterProcedureEnv env, RegionStateNode regionNode)
    throws IOException, ProcedureSuspendedException {
    if (
      ProcedureFutureUtil.checkFuture(this, this::getFuture, this::setFuture,
        () -> regionFailedOpenAfterUpdatingMeta(env, regionNode))
    ) {
      return Flow.NO_MORE_STATE;
    }
    if (regionNode.isInState(State.OPEN)) {
      retryCounter = null;
      if (lastState == RegionStateTransitionState.REGION_STATE_TRANSITION_CONFIRM_OPENED) {
        // we are the last state, finish
        regionNode.unsetProcedure(this);
        ServerCrashProcedure.updateProgress(env, getParentProcId());
        return Flow.NO_MORE_STATE;
      }
      // It is possible that we arrive here but confirm opened is not the last state, for example,
      // when merging or splitting a region, we unassign the region from a RS and the RS is crashed,
      // then there will be recovered edits for this region, we'd better make the region online
      // again and then unassign it, otherwise we have to fail the merge/split procedure as we may
      // loss data.
      setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_CLOSE);
      return Flow.HAS_MORE_STATE;
    }

    int retries = env.getAssignmentManager().getRegionStates().addToFailedOpen(regionNode)
      .incrementAndGetRetries();
    int maxAttempts = env.getAssignmentManager().getAssignMaxAttempts();
    LOG.info("Retry={} of max={}; {}; {}", retries, maxAttempts, this, regionNode.toShortString());

    if (retries >= maxAttempts) {
      ProcedureFutureUtil.suspendIfNecessary(this, this::setFuture,
        env.getAssignmentManager().regionFailedOpen(regionNode, true), env,
        () -> regionFailedOpenAfterUpdatingMeta(env, regionNode));
      return Flow.NO_MORE_STATE;
    }

    // if not giving up, we will not update meta, so the returned CompletableFuture should be a fake
    // one, which should have been completed already
    CompletableFuture<Void> future = env.getAssignmentManager().regionFailedOpen(regionNode, false);
    assert future.isDone();
    // we failed to assign the region, force a new plan
    forceNewPlan = true;
    regionNode.setRegionLocation(null);
    setNextState(RegionStateTransitionState.REGION_STATE_TRANSITION_GET_ASSIGN_CANDIDATE);

    if (retries > env.getAssignmentManager().getAssignRetryImmediatelyMaxAttempts()) {
      // Throw exception to backoff and retry when failed open too many times
      throw new HBaseIOException(
        "Failed confirm OPEN of " + regionNode + " (remote log may yield more detail on why).");
    } else {
      // Here we do not throw exception because we want to the region to be online ASAP
      return Flow.HAS_MORE_STATE;
    }
  }
The procedure to deal with the state transition of a region. A region with a TRSP in place is
called RIT, i.e, RegionInTransition.
<p/>
It can be used to assign/unassign/reopen/move a region, and for
{@link #unassign(MasterProcedureEnv, RegionInfo)} and
{@link #reopen(MasterProcedureEnv, RegionInfo)}, you do not need to specify a target server, and
for {@link #assign(MasterProcedureEnv, RegionInfo, ServerName)} and
{@link #move(MasterProcedureEnv, RegionInfo, ServerName)}, if you want to you can provide a
target server. And for {@link #move(MasterProcedureEnv, RegionInfo, ServerName)}, if you do not
specify a targetServer, we will select one randomly.
<p/>
<p/>
The typical state transition for assigning a region is:

<pre>
GET_ASSIGN_CANDIDATE ------> OPEN -----> CONFIRM_OPENED
</pre>

Notice that, if there are failures we may go back to the {@code GET_ASSIGN_CANDIDATE} state to
try again.
<p/>
The typical state transition for unassigning a region is:

<pre>
CLOSE -----> CONFIRM_CLOSED
</pre>

Here things go a bit different, if there are failures, especially that if there is a server
crash, we will go to the {@code GET_ASSIGN_CANDIDATE} state to bring the region online first, and
then go through the normal way to unassign it.
<p/>
The typical state transition for reopening/moving a region is:

<pre>
CLOSE -----> CONFIRM_CLOSED -----> GET_ASSIGN_CANDIDATE ------> OPEN -----> CONFIRM_OPENED
</pre>

The retry logic is the same with the above assign/unassign.
<p/>
Notice that, although we allow specify a target server, it just acts as a candidate, we do not
guarantee that the region will finally be on the target server. If this is important for you, you
should check whether the region is on the target server after the procedure is finished.
</p>
Altenatively, for trying retaining assignments, the
<b>hbase.master.scp.retain.assignment.force</b> option can be used together with
<b>hbase.master.scp.retain.assignment</b>.
<p/>
When you want to schedule a TRSP, please check whether there is still one for this region, and
the check should be under the RegionStateNode lock. We will remove the TRSP from a
RegionStateNode when we are done, see the code in {@code reportTransition} method below. There
could be at most one TRSP for a give region.
hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/TransitRegionStateProcedure.java