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 java.util.Collections;
022import java.util.List;
023import java.util.stream.Collectors;
024import org.apache.hadoop.hbase.HBaseIOException;
025import org.apache.hadoop.hbase.HRegionLocation;
026import org.apache.hadoop.hbase.TableName;
027import org.apache.hadoop.hbase.master.RegionPlan;
028import org.apache.hadoop.hbase.master.assignment.MoveRegionProcedure;
029import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
030import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException;
031import org.apache.hadoop.hbase.procedure2.ProcedureUtil;
032import org.apache.hadoop.hbase.procedure2.ProcedureYieldException;
033import org.apache.yetus.audience.InterfaceAudience;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
038import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.ReopenTableRegionsState;
039import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.ReopenTableRegionsStateData;
040import org.apache.hadoop.hbase.shaded.protobuf.generated.ProcedureProtos;
041
042/**
043 * Used for reopening the regions for a table.
044 * <p/>
045 * Currently we use {@link MoveRegionProcedure} to reopen regions.
046 */
047@InterfaceAudience.Private
048public class ReopenTableRegionsProcedure
049    extends AbstractStateMachineTableProcedure<ReopenTableRegionsState> {
050
051  private static final Logger LOG = LoggerFactory.getLogger(ReopenTableRegionsProcedure.class);
052
053  private TableName tableName;
054
055  private List<HRegionLocation> regions = Collections.emptyList();
056
057  private int attempt;
058
059  public ReopenTableRegionsProcedure() {
060  }
061
062  public ReopenTableRegionsProcedure(TableName tableName) {
063    this.tableName = tableName;
064  }
065
066  @Override
067  public TableName getTableName() {
068    return tableName;
069  }
070
071  @Override
072  public TableOperationType getTableOperationType() {
073    return TableOperationType.REGION_EDIT;
074  }
075
076  private MoveRegionProcedure createReopenProcedure(MasterProcedureEnv env, HRegionLocation loc) {
077    try {
078      return new MoveRegionProcedure(env,
079        new RegionPlan(loc.getRegion(), loc.getServerName(), loc.getServerName()), false);
080    } catch (HBaseIOException e) {
081      // we skip the checks so this should not happen
082      throw new AssertionError(e);
083    }
084  }
085
086  @Override
087  protected Flow executeFromState(MasterProcedureEnv env, ReopenTableRegionsState state)
088      throws ProcedureSuspendedException, ProcedureYieldException, InterruptedException {
089    switch (state) {
090      case REOPEN_TABLE_REGIONS_GET_REGIONS:
091        if (!env.getAssignmentManager().isTableEnabled(tableName)) {
092          LOG.info("Table {} is disabled, give up reopening its regions");
093          return Flow.NO_MORE_STATE;
094        }
095        regions =
096          env.getAssignmentManager().getRegionStates().getRegionsOfTableForReopen(tableName);
097        setNextState(ReopenTableRegionsState.REOPEN_TABLE_REGIONS_REOPEN_REGIONS);
098        return Flow.HAS_MORE_STATE;
099      case REOPEN_TABLE_REGIONS_REOPEN_REGIONS:
100        addChildProcedure(regions.stream().filter(l -> l.getSeqNum() >= 0)
101          .map(l -> createReopenProcedure(env, l)).toArray(MoveRegionProcedure[]::new));
102        setNextState(ReopenTableRegionsState.REOPEN_TABLE_REGIONS_CONFIRM_REOPENED);
103        return Flow.HAS_MORE_STATE;
104      case REOPEN_TABLE_REGIONS_CONFIRM_REOPENED:
105        regions = regions.stream().map(env.getAssignmentManager().getRegionStates()::checkReopened)
106          .filter(l -> l != null).collect(Collectors.toList());
107        if (regions.isEmpty()) {
108          return Flow.NO_MORE_STATE;
109        }
110        if (regions.stream().anyMatch(l -> l.getSeqNum() >= 0)) {
111          attempt = 0;
112          setNextState(ReopenTableRegionsState.REOPEN_TABLE_REGIONS_REOPEN_REGIONS);
113          return Flow.HAS_MORE_STATE;
114        }
115        // All the regions need to reopen are in OPENING state which means we can not schedule any
116        // MRPs.
117        long backoff = ProcedureUtil.getBackoffTimeMs(this.attempt++);
118        LOG.info(
119          "There are still {} region(s) which need to be reopened for table {} are in " +
120            "OPENING state, suspend {}secs and try again later",
121          regions.size(), tableName, backoff / 1000);
122        setTimeout(Math.toIntExact(backoff));
123        setState(ProcedureProtos.ProcedureState.WAITING_TIMEOUT);
124        throw new ProcedureSuspendedException();
125      default:
126        throw new UnsupportedOperationException("unhandled state=" + state);
127    }
128  }
129
130  /**
131   * At end of timeout, wake ourselves up so we run again.
132   */
133  @Override
134  protected synchronized boolean setTimeoutFailure(MasterProcedureEnv env) {
135    setState(ProcedureProtos.ProcedureState.RUNNABLE);
136    env.getProcedureScheduler().addFront(this);
137    return false; // 'false' means that this procedure handled the timeout
138  }
139  @Override
140  protected void rollbackState(MasterProcedureEnv env, ReopenTableRegionsState state)
141      throws IOException, InterruptedException {
142    throw new UnsupportedOperationException("unhandled state=" + state);
143  }
144
145  @Override
146  protected ReopenTableRegionsState getState(int stateId) {
147    return ReopenTableRegionsState.forNumber(stateId);
148  }
149
150  @Override
151  protected int getStateId(ReopenTableRegionsState state) {
152    return state.getNumber();
153  }
154
155  @Override
156  protected ReopenTableRegionsState getInitialState() {
157    return ReopenTableRegionsState.REOPEN_TABLE_REGIONS_GET_REGIONS;
158  }
159
160  @Override
161  protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException {
162    super.serializeStateData(serializer);
163    ReopenTableRegionsStateData.Builder builder = ReopenTableRegionsStateData.newBuilder()
164      .setTableName(ProtobufUtil.toProtoTableName(tableName));
165    regions.stream().map(ProtobufUtil::toRegionLocation).forEachOrdered(builder::addRegion);
166    serializer.serialize(builder.build());
167  }
168
169  @Override
170  protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException {
171    super.deserializeStateData(serializer);
172    ReopenTableRegionsStateData data = serializer.deserialize(ReopenTableRegionsStateData.class);
173    tableName = ProtobufUtil.toTableName(data.getTableName());
174    regions = data.getRegionList().stream().map(ProtobufUtil::toRegionLocation)
175      .collect(Collectors.toList());
176  }
177}