001/*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020package org.apache.hadoop.hbase.util;
021
022import org.apache.hadoop.hbase.HRegionLocation;
023import org.apache.hadoop.hbase.ServerName;
024import org.apache.hadoop.hbase.client.Admin;
025import org.apache.hadoop.hbase.client.Connection;
026import org.apache.hadoop.hbase.client.RegionInfo;
027import org.apache.hadoop.hbase.client.ResultScanner;
028import org.apache.hadoop.hbase.client.Scan;
029import org.apache.hadoop.hbase.client.Table;
030import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
031import org.apache.yetus.audience.InterfaceAudience;
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035import java.io.IOException;
036import java.util.List;
037import java.util.concurrent.Callable;
038
039/**
040 * Move Regions and make sure that they are up on the target server.If a region movement fails we
041 * exit as failure
042 */
043@InterfaceAudience.Private
044class MoveWithAck implements Callable<Boolean> {
045
046  private static final Logger LOG = LoggerFactory.getLogger(MoveWithAck.class);
047
048  private final RegionInfo region;
049  private final ServerName targetServer;
050  private final List<RegionInfo> movedRegions;
051  private final ServerName sourceServer;
052  private final Connection conn;
053  private final Admin admin;
054
055  MoveWithAck(Connection conn, RegionInfo regionInfo, ServerName sourceServer,
056    ServerName targetServer, List<RegionInfo> movedRegions) throws IOException {
057    this.conn = conn;
058    this.region = regionInfo;
059    this.targetServer = targetServer;
060    this.movedRegions = movedRegions;
061    this.sourceServer = sourceServer;
062    this.admin = conn.getAdmin();
063  }
064
065  @Override
066  public Boolean call() throws IOException, InterruptedException {
067    boolean moved = false;
068    int count = 0;
069    int retries = admin.getConfiguration()
070      .getInt(RegionMover.MOVE_RETRIES_MAX_KEY, RegionMover.DEFAULT_MOVE_RETRIES_MAX);
071    int maxWaitInSeconds = admin.getConfiguration()
072      .getInt(RegionMover.MOVE_WAIT_MAX_KEY, RegionMover.DEFAULT_MOVE_WAIT_MAX);
073    long startTime = EnvironmentEdgeManager.currentTime();
074    boolean sameServer = true;
075    // Assert we can scan the region in its current location
076    isSuccessfulScan(region);
077    LOG.info("Moving region: {} from {} to {}", region.getRegionNameAsString(), sourceServer,
078      targetServer);
079    while (count < retries && sameServer) {
080      if (count > 0) {
081        LOG.debug("Retry {} of maximum {} for region: {}", count, retries,
082          region.getRegionNameAsString());
083      }
084      count = count + 1;
085      admin.move(region.getEncodedNameAsBytes(), targetServer);
086      long maxWait = startTime + (maxWaitInSeconds * 1000);
087      while (EnvironmentEdgeManager.currentTime() < maxWait) {
088        sameServer = isSameServer(region, sourceServer);
089        if (!sameServer) {
090          break;
091        }
092        Thread.sleep(1000);
093      }
094    }
095    if (sameServer) {
096      LOG.error("Region: {} stuck on {} for {} sec , newServer={}", region.getRegionNameAsString(),
097        this.sourceServer, getTimeDiffInSec(startTime), this.targetServer);
098    } else {
099      isSuccessfulScan(region);
100      LOG.info("Moved Region {} , cost (sec): {}", region.getRegionNameAsString(),
101        getTimeDiffInSec(startTime));
102      moved = true;
103      movedRegions.add(region);
104    }
105    return moved;
106  }
107
108  private static String getTimeDiffInSec(long startTime) {
109    return String.format("%.3f", (float) (EnvironmentEdgeManager.currentTime() - startTime) / 1000);
110  }
111
112  /**
113   * Tries to scan a row from passed region
114   */
115  private void isSuccessfulScan(RegionInfo region) throws IOException {
116    Scan scan = new Scan().withStartRow(region.getStartKey()).setRaw(true).setOneRowLimit()
117      .setMaxResultSize(1L).setCaching(1).setFilter(new FirstKeyOnlyFilter())
118      .setCacheBlocks(false);
119    try (Table table = conn.getTable(region.getTable());
120      ResultScanner scanner = table.getScanner(scan)) {
121      scanner.next();
122    } catch (IOException e) {
123      LOG.error("Could not scan region: {}", region.getEncodedName(), e);
124      throw e;
125    }
126  }
127
128  /**
129   * Returns true if passed region is still on serverName when we look at hbase:meta.
130   * @return true if region is hosted on serverName otherwise false
131   */
132  private boolean isSameServer(RegionInfo region, ServerName serverName)
133    throws IOException {
134    ServerName serverForRegion = getServerNameForRegion(region, admin, conn);
135    return serverForRegion != null && serverForRegion.equals(serverName);
136  }
137
138  /**
139   * Get servername that is up in hbase:meta hosting the given region. this is hostname + port +
140   * startcode comma-delimited. Can return null
141   * @return regionServer hosting the given region
142   */
143  static ServerName getServerNameForRegion(RegionInfo region, Admin admin, Connection conn)
144      throws IOException {
145    if (!admin.isTableEnabled(region.getTable())) {
146      return null;
147    }
148    HRegionLocation loc;
149    try {
150      loc = conn.getRegionLocator(region.getTable())
151        .getRegionLocation(region.getStartKey(), region.getReplicaId(), true);
152    } catch (IOException e) {
153      if (e.getMessage() != null && e.getMessage().startsWith("Unable to find region for")) {
154        return null;
155      }
156      throw e;
157    }
158    if (loc != null) {
159      return loc.getServerName();
160    } else {
161      return null;
162    }
163  }
164
165}