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 static org.apache.hadoop.hbase.HConstants.DEFAULT_HBASE_ENABLE_SEPARATE_CHILD_REGIONS;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.List;
026import java.util.ListIterator;
027import java.util.stream.Collectors;
028import java.util.stream.IntStream;
029import java.util.stream.Stream;
030import org.apache.commons.lang3.ArrayUtils;
031import org.apache.hadoop.hbase.HBaseIOException;
032import org.apache.hadoop.hbase.HConstants;
033import org.apache.hadoop.hbase.ServerName;
034import org.apache.hadoop.hbase.client.RegionInfo;
035import org.apache.hadoop.hbase.client.RegionReplicaUtil;
036import org.apache.hadoop.hbase.favored.FavoredNodesManager;
037import org.apache.hadoop.hbase.ipc.HBaseRpcController;
038import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
039import org.apache.hadoop.hbase.wal.WALSplitUtil;
040import org.apache.yetus.audience.InterfaceAudience;
041
042import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
043
044import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
045import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
046import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.AdminService;
047import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoRequest;
048import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoResponse;
049
050/**
051 * Utility for this assignment package only.
052 */
053@InterfaceAudience.Private
054final class AssignmentManagerUtil {
055  private static final int DEFAULT_REGION_REPLICA = 1;
056
057  private AssignmentManagerUtil() {
058  }
059
060  /**
061   * Raw call to remote regionserver to get info on a particular region.
062   * @throws IOException Let it out so can report this IOE as reason for failure
063   */
064  static GetRegionInfoResponse getRegionInfoResponse(final MasterProcedureEnv env,
065    final ServerName regionLocation, final RegionInfo hri) throws IOException {
066    return getRegionInfoResponse(env, regionLocation, hri, false);
067  }
068
069  static GetRegionInfoResponse getRegionInfoResponse(final MasterProcedureEnv env,
070    final ServerName regionLocation, final RegionInfo hri, boolean includeBestSplitRow)
071    throws IOException {
072    // TODO: There is no timeout on this controller. Set one!
073    HBaseRpcController controller =
074      env.getMasterServices().getClusterConnection().getRpcControllerFactory().newController();
075    final AdminService.BlockingInterface admin =
076      env.getMasterServices().getClusterConnection().getAdmin(regionLocation);
077    GetRegionInfoRequest request = null;
078    if (includeBestSplitRow) {
079      request = RequestConverter.buildGetRegionInfoRequest(hri.getRegionName(), false, true);
080    } else {
081      request = RequestConverter.buildGetRegionInfoRequest(hri.getRegionName());
082    }
083    try {
084      return admin.getRegionInfo(controller, request);
085    } catch (ServiceException e) {
086      throw ProtobufUtil.handleRemoteException(e);
087    }
088  }
089
090  private static void lock(List<RegionStateNode> regionNodes) {
091    regionNodes.iterator().forEachRemaining(RegionStateNode::lock);
092  }
093
094  private static void unlock(List<RegionStateNode> regionNodes) {
095    for (ListIterator<RegionStateNode> iter = regionNodes.listIterator(regionNodes.size()); iter
096      .hasPrevious();) {
097      iter.previous().unlock();
098    }
099  }
100
101  static TransitRegionStateProcedure[] createUnassignProceduresForSplitOrMerge(
102    MasterProcedureEnv env, Stream<RegionInfo> regions, int regionReplication) throws IOException {
103    List<RegionStateNode> regionNodes = regions
104      .flatMap(hri -> IntStream.range(0, regionReplication)
105        .mapToObj(i -> RegionReplicaUtil.getRegionInfoForReplica(hri, i)))
106      .map(env.getAssignmentManager().getRegionStates()::getOrCreateRegionStateNode)
107      .collect(Collectors.toList());
108    TransitRegionStateProcedure[] procs = new TransitRegionStateProcedure[regionNodes.size()];
109    boolean rollback = true;
110    int i = 0;
111    // hold the lock at once, and then release it in finally. This is important as SCP may jump in
112    // if we release the lock in the middle when we want to do rollback, and cause problems.
113    lock(regionNodes);
114    try {
115      for (; i < procs.length; i++) {
116        RegionStateNode regionNode = regionNodes.get(i);
117        TransitRegionStateProcedure proc =
118          TransitRegionStateProcedure.unassignSplitMerge(env, regionNode.getRegionInfo());
119        if (regionNode.getProcedure() != null) {
120          throw new HBaseIOException(
121            "The parent region " + regionNode + " is currently in transition, give up");
122        }
123        regionNode.setProcedure(proc);
124        procs[i] = proc;
125      }
126      // all succeeded, set rollback to false
127      rollback = false;
128    } finally {
129      if (rollback) {
130        for (;;) {
131          i--;
132          if (i < 0) {
133            break;
134          }
135          RegionStateNode regionNode = regionNodes.get(i);
136          regionNode.unsetProcedure(procs[i]);
137        }
138      }
139      unlock(regionNodes);
140    }
141    return procs;
142  }
143
144  /**
145   * Create assign procedures for the give regions, according to the {@code regionReplication}.
146   * <p/>
147   * For rolling back, we will submit procedures directly to the {@code ProcedureExecutor}, so it is
148   * possible that we persist the newly scheduled procedures, and then crash before persisting the
149   * rollback state, so when we arrive here the second time, it is possible that some regions have
150   * already been associated with a TRSP.
151   * @param ignoreIfInTransition if true, will skip creating TRSP for the given region if it is
152   *                             already in transition, otherwise we will add an assert that it
153   *                             should not in transition.
154   */
155  private static TransitRegionStateProcedure[] createAssignProcedures(MasterProcedureEnv env,
156    List<RegionInfo> regions, int regionReplication, ServerName targetServer,
157    boolean ignoreIfInTransition) {
158    // create the assign procs only for the primary region using the targetServer
159    TransitRegionStateProcedure[] primaryRegionProcs =
160      regions.stream().map(env.getAssignmentManager().getRegionStates()::getOrCreateRegionStateNode)
161        .map(regionNode -> {
162          TransitRegionStateProcedure proc =
163            TransitRegionStateProcedure.assign(env, regionNode.getRegionInfo(), targetServer);
164          regionNode.lock();
165          try {
166            if (ignoreIfInTransition) {
167              if (regionNode.isInTransition()) {
168                return null;
169              }
170            } else {
171              // should never fail, as we have the exclusive region lock, and the region is newly
172              // created, or has been successfully closed so should not be on any servers, so SCP
173              // will
174              // not process it either.
175              assert !regionNode.isInTransition();
176            }
177            regionNode.setProcedure(proc);
178          } finally {
179            regionNode.unlock();
180          }
181          return proc;
182        }).filter(p -> p != null).toArray(TransitRegionStateProcedure[]::new);
183    if (regionReplication == DEFAULT_REGION_REPLICA) {
184      // this is the default case
185      return primaryRegionProcs;
186    }
187    // collect the replica region infos
188    List<RegionInfo> replicaRegionInfos =
189      new ArrayList<RegionInfo>(regions.size() * (regionReplication - 1));
190    for (RegionInfo hri : regions) {
191      // start the index from 1
192      for (int i = 1; i < regionReplication; i++) {
193        RegionInfo ri = RegionReplicaUtil.getRegionInfoForReplica(hri, i);
194        // apply ignoreRITs to replica regions as well.
195        if (
196          !ignoreIfInTransition || !env.getAssignmentManager().getRegionStates()
197            .getOrCreateRegionStateNode(ri).isInTransition()
198        ) {
199          replicaRegionInfos.add(ri);
200        }
201      }
202    }
203
204    // create round robin procs. Note that we exclude the primary region's target server
205    TransitRegionStateProcedure[] replicaRegionAssignProcs =
206      env.getAssignmentManager().createRoundRobinAssignProcedures(replicaRegionInfos,
207        Collections.singletonList(targetServer));
208    // combine both the procs and return the result
209    return ArrayUtils.addAll(primaryRegionProcs, replicaRegionAssignProcs);
210  }
211
212  /**
213   * Create round robin assign procedures for the given regions, according to the
214   * {@code regionReplication}.
215   * <p/>
216   * For rolling back, we will submit procedures directly to the {@code ProcedureExecutor}, so it is
217   * possible that we persist the newly scheduled procedures, and then crash before persisting the
218   * rollback state, so when we arrive here the second time, it is possible that some regions have
219   * already been associated with a TRSP.
220   * @param ignoreIfInTransition if true, will skip creating TRSP for the given region if it is
221   *                             already in transition, otherwise we will add an assert that it
222   *                             should not in transition.
223   */
224  private static TransitRegionStateProcedure[] createRoundRobinAssignProcedures(
225    MasterProcedureEnv env, List<RegionInfo> regions, int regionReplication,
226    List<ServerName> serversToExclude, boolean ignoreIfInTransition) {
227    List<RegionInfo> regionsAndReplicas = new ArrayList<>(regions);
228    if (regionReplication != DEFAULT_REGION_REPLICA) {
229
230      // collect the replica region infos
231      List<RegionInfo> replicaRegionInfos =
232        new ArrayList<RegionInfo>(regions.size() * (regionReplication - 1));
233      for (RegionInfo hri : regions) {
234        // start the index from 1
235        for (int i = 1; i < regionReplication; i++) {
236          replicaRegionInfos.add(RegionReplicaUtil.getRegionInfoForReplica(hri, i));
237        }
238      }
239      regionsAndReplicas.addAll(replicaRegionInfos);
240    }
241    if (ignoreIfInTransition) {
242      for (RegionInfo region : regionsAndReplicas) {
243        if (
244          env.getAssignmentManager().getRegionStates().getOrCreateRegionStateNode(region)
245            .isInTransition()
246        ) {
247          return null;
248        }
249      }
250    }
251    // create round robin procs. Note that we exclude the primary region's target server
252    return env.getAssignmentManager().createRoundRobinAssignProcedures(regionsAndReplicas,
253      serversToExclude);
254  }
255
256  static TransitRegionStateProcedure[] createAssignProceduresForSplitDaughters(
257    MasterProcedureEnv env, List<RegionInfo> daughters, int regionReplication,
258    ServerName parentServer) {
259    if (
260      env.getMasterConfiguration().getBoolean(HConstants.HBASE_ENABLE_SEPARATE_CHILD_REGIONS,
261        DEFAULT_HBASE_ENABLE_SEPARATE_CHILD_REGIONS)
262    ) {
263      // keep one daughter on the parent region server
264      TransitRegionStateProcedure[] daughterOne = createAssignProcedures(env,
265        Collections.singletonList(daughters.get(0)), regionReplication, parentServer, false);
266      // round robin assign the other daughter
267      TransitRegionStateProcedure[] daughterTwo =
268        createRoundRobinAssignProcedures(env, Collections.singletonList(daughters.get(1)),
269          regionReplication, Collections.singletonList(parentServer), false);
270      return ArrayUtils.addAll(daughterOne, daughterTwo);
271    }
272    return createAssignProceduresForOpeningNewRegions(env, daughters, regionReplication,
273      parentServer);
274  }
275
276  static TransitRegionStateProcedure[] createAssignProceduresForOpeningNewRegions(
277    MasterProcedureEnv env, List<RegionInfo> regions, int regionReplication,
278    ServerName targetServer) {
279    return createAssignProcedures(env, regions, regionReplication, targetServer, false);
280  }
281
282  static void reopenRegionsForRollback(MasterProcedureEnv env, List<RegionInfo> regions,
283    int regionReplication, ServerName targetServer) {
284    TransitRegionStateProcedure[] procs =
285      createAssignProcedures(env, regions, regionReplication, targetServer, true);
286    if (procs.length > 0) {
287      env.getMasterServices().getMasterProcedureExecutor().submitProcedures(procs);
288    }
289  }
290
291  static void removeNonDefaultReplicas(MasterProcedureEnv env, Stream<RegionInfo> regions,
292    int regionReplication) {
293    // Remove from in-memory states
294    regions.flatMap(hri -> IntStream.range(1, regionReplication)
295      .mapToObj(i -> RegionReplicaUtil.getRegionInfoForReplica(hri, i))).forEach(hri -> {
296        env.getAssignmentManager().getRegionStates().deleteRegion(hri);
297        env.getMasterServices().getServerManager().removeRegion(hri);
298        FavoredNodesManager fnm = env.getMasterServices().getFavoredNodesManager();
299        if (fnm != null) {
300          fnm.deleteFavoredNodesForRegions(Collections.singletonList(hri));
301        }
302      });
303  }
304
305  static void checkClosedRegion(MasterProcedureEnv env, RegionInfo regionInfo) throws IOException {
306    if (WALSplitUtil.hasRecoveredEdits(env.getMasterConfiguration(), regionInfo)) {
307      throw new IOException("Recovered.edits are found in Region: " + regionInfo
308        + ", abort split/merge to prevent data loss");
309    }
310  }
311}