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 java.io.BufferedInputStream;
023import java.io.BufferedOutputStream;
024import java.io.Closeable;
025import java.io.DataInputStream;
026import java.io.DataOutputStream;
027import java.io.File;
028import java.io.FileInputStream;
029import java.io.FileOutputStream;
030import java.io.IOException;
031import java.nio.file.Files;
032import java.nio.file.Paths;
033import java.util.ArrayList;
034import java.util.Collections;
035import java.util.EnumSet;
036import java.util.HashSet;
037import java.util.Iterator;
038import java.util.List;
039import java.util.Locale;
040import java.util.Set;
041import java.util.concurrent.CancellationException;
042import java.util.concurrent.ExecutionException;
043import java.util.concurrent.ExecutorService;
044import java.util.concurrent.Executors;
045import java.util.concurrent.Future;
046import java.util.concurrent.TimeUnit;
047import java.util.concurrent.TimeoutException;
048import java.util.function.Predicate;
049import org.apache.commons.io.IOUtils;
050import org.apache.hadoop.conf.Configuration;
051import org.apache.hadoop.hbase.ClusterMetrics.Option;
052import org.apache.hadoop.hbase.HBaseConfiguration;
053import org.apache.hadoop.hbase.HConstants;
054import org.apache.hadoop.hbase.ServerName;
055import org.apache.hadoop.hbase.UnknownRegionException;
056import org.apache.hadoop.hbase.client.Admin;
057import org.apache.hadoop.hbase.client.Connection;
058import org.apache.hadoop.hbase.client.ConnectionFactory;
059import org.apache.hadoop.hbase.client.DoNotRetryRegionException;
060import org.apache.hadoop.hbase.client.RegionInfo;
061import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
062import org.apache.yetus.audience.InterfaceAudience;
063import org.slf4j.Logger;
064import org.slf4j.LoggerFactory;
065
066import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
067import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils;
068
069/**
070 * Tool for loading/unloading regions to/from given regionserver This tool can be run from Command
071 * line directly as a utility. Supports Ack/No Ack mode for loading/unloading operations.Ack mode
072 * acknowledges if regions are online after movement while noAck mode is best effort mode that
073 * improves performance but will still move on if region is stuck/not moved. Motivation behind noAck
074 * mode being RS shutdown where even if a Region is stuck, upon shutdown master will move it
075 * anyways. This can also be used by constructiong an Object using the builder and then calling
076 * {@link #load()} or {@link #unload()} methods for the desired operations.
077 */
078@InterfaceAudience.Public
079public class RegionMover extends AbstractHBaseTool implements Closeable {
080  public static final String MOVE_RETRIES_MAX_KEY = "hbase.move.retries.max";
081  public static final String MOVE_WAIT_MAX_KEY = "hbase.move.wait.max";
082  public static final String SERVERSTART_WAIT_MAX_KEY = "hbase.serverstart.wait.max";
083  public static final int DEFAULT_MOVE_RETRIES_MAX = 5;
084  public static final int DEFAULT_MOVE_WAIT_MAX = 60;
085  public static final int DEFAULT_SERVERSTART_WAIT_MAX = 180;
086
087  private static final Logger LOG = LoggerFactory.getLogger(RegionMover.class);
088
089  private RegionMoverBuilder rmbuilder;
090  private boolean ack = true;
091  private int maxthreads = 1;
092  private int timeout;
093  private String loadUnload;
094  private String hostname;
095  private String filename;
096  private String excludeFile;
097  private String designatedFile;
098  private int port;
099  private Connection conn;
100  private Admin admin;
101
102  private RegionMover(RegionMoverBuilder builder) throws IOException {
103    this.hostname = builder.hostname;
104    this.filename = builder.filename;
105    this.excludeFile = builder.excludeFile;
106    this.designatedFile = builder.designatedFile;
107    this.maxthreads = builder.maxthreads;
108    this.ack = builder.ack;
109    this.port = builder.port;
110    this.timeout = builder.timeout;
111    setConf(builder.conf);
112    this.conn = ConnectionFactory.createConnection(conf);
113    this.admin = conn.getAdmin();
114  }
115
116  private RegionMover() {
117  }
118
119  @Override
120  public void close() {
121    IOUtils.closeQuietly(this.admin);
122    IOUtils.closeQuietly(this.conn);
123  }
124
125  /**
126   * Builder for Region mover. Use the {@link #build()} method to create RegionMover object. Has
127   * {@link #filename(String)}, {@link #excludeFile(String)}, {@link #maxthreads(int)},
128   * {@link #ack(boolean)}, {@link #timeout(int)}, {@link #designatedFile(String)} methods to set
129   * the corresponding options.
130   */
131  public static class RegionMoverBuilder {
132    private boolean ack = true;
133    private int maxthreads = 1;
134    private int timeout = Integer.MAX_VALUE;
135    private String hostname;
136    private String filename;
137    private String excludeFile = null;
138    private String designatedFile = null;
139    private String defaultDir = System.getProperty("java.io.tmpdir");
140    @InterfaceAudience.Private
141    final int port;
142    private final Configuration conf;
143
144    public RegionMoverBuilder(String hostname) {
145      this(hostname, createConf());
146    }
147
148    /**
149     * Creates a new configuration and sets region mover specific overrides
150     */
151    private static Configuration createConf() {
152      Configuration conf = HBaseConfiguration.create();
153      conf.setInt("hbase.client.prefetch.limit", 1);
154      conf.setInt("hbase.client.pause", 500);
155      conf.setInt("hbase.client.retries.number", 100);
156      return conf;
157    }
158
159    /**
160     * @param hostname Hostname to unload regions from or load regions to. Can be either hostname
161     *     or hostname:port.
162     * @param conf Configuration object
163     */
164    public RegionMoverBuilder(String hostname, Configuration conf) {
165      String[] splitHostname = hostname.toLowerCase().split(":");
166      this.hostname = splitHostname[0];
167      if (splitHostname.length == 2) {
168        this.port = Integer.parseInt(splitHostname[1]);
169      } else {
170        this.port = conf.getInt(HConstants.REGIONSERVER_PORT, HConstants.DEFAULT_REGIONSERVER_PORT);
171      }
172      this.filename = defaultDir + File.separator + System.getProperty("user.name") + this.hostname
173        + ":" + Integer.toString(this.port);
174      this.conf = conf;
175    }
176
177    /**
178     * Path of file where regions will be written to during unloading/read from during loading
179     * @param filename
180     * @return RegionMoverBuilder object
181     */
182    public RegionMoverBuilder filename(String filename) {
183      this.filename = filename;
184      return this;
185    }
186
187    /**
188     * Set the max number of threads that will be used to move regions
189     */
190    public RegionMoverBuilder maxthreads(int threads) {
191      this.maxthreads = threads;
192      return this;
193    }
194
195    /**
196     * Path of file containing hostnames to be excluded during region movement. Exclude file should
197     * have 'host:port' per line. Port is mandatory here as we can have many RS running on a single
198     * host.
199     */
200    public RegionMoverBuilder excludeFile(String excludefile) {
201      this.excludeFile = excludefile;
202      return this;
203    }
204
205    /**
206     * Set the designated file. Designated file contains hostnames where region moves. Designated
207     * file should have 'host:port' per line. Port is mandatory here as we can have many RS running
208     * on a single host.
209     * @param designatedFile The designated file
210     * @return RegionMoverBuilder object
211     */
212    public RegionMoverBuilder designatedFile(String designatedFile) {
213      this.designatedFile = designatedFile;
214      return this;
215    }
216
217    /**
218     * Set ack/noAck mode.
219     * <p>
220     * In ack mode regions are acknowledged before and after moving and the move is retried
221     * hbase.move.retries.max times, if unsuccessful we quit with exit code 1.No Ack mode is a best
222     * effort mode,each region movement is tried once.This can be used during graceful shutdown as
223     * even if we have a stuck region,upon shutdown it'll be reassigned anyway.
224     * <p>
225     * @param ack
226     * @return RegionMoverBuilder object
227     */
228    public RegionMoverBuilder ack(boolean ack) {
229      this.ack = ack;
230      return this;
231    }
232
233    /**
234     * Set the timeout for Load/Unload operation in seconds.This is a global timeout,threadpool for
235     * movers also have a separate time which is hbase.move.wait.max * number of regions to
236     * load/unload
237     * @param timeout in seconds
238     * @return RegionMoverBuilder object
239     */
240    public RegionMoverBuilder timeout(int timeout) {
241      this.timeout = timeout;
242      return this;
243    }
244
245    /**
246     * This method builds the appropriate RegionMover object which can then be used to load/unload
247     * using load and unload methods
248     * @return RegionMover object
249     */
250    public RegionMover build() throws IOException {
251      return new RegionMover(this);
252    }
253  }
254
255  /**
256   * Loads the specified {@link #hostname} with regions listed in the {@link #filename} RegionMover
257   * Object has to be created using {@link #RegionMover(RegionMoverBuilder)}
258   * @return true if loading succeeded, false otherwise
259   */
260  public boolean load() throws ExecutionException, InterruptedException, TimeoutException {
261    ExecutorService loadPool = Executors.newFixedThreadPool(1);
262    Future<Boolean> loadTask = loadPool.submit(() -> {
263      try {
264        List<RegionInfo> regionsToMove = readRegionsFromFile(filename);
265        if (regionsToMove.isEmpty()) {
266          LOG.info("No regions to load.Exiting");
267          return true;
268        }
269        loadRegions(regionsToMove);
270      } catch (Exception e) {
271        LOG.error("Error while loading regions to " + hostname, e);
272        return false;
273      }
274      return true;
275    });
276    return waitTaskToFinish(loadPool, loadTask, "loading");
277  }
278
279  private void loadRegions(List<RegionInfo> regionsToMove)
280      throws Exception {
281    ServerName server = getTargetServer();
282    List<RegionInfo> movedRegions = Collections.synchronizedList(new ArrayList<>());
283    LOG.info(
284        "Moving " + regionsToMove.size() + " regions to " + server + " using " + this.maxthreads
285            + " threads.Ack mode:" + this.ack);
286
287    final ExecutorService moveRegionsPool = Executors.newFixedThreadPool(this.maxthreads);
288    List<Future<Boolean>> taskList = new ArrayList<>();
289    int counter = 0;
290    while (counter < regionsToMove.size()) {
291      RegionInfo region = regionsToMove.get(counter);
292      ServerName currentServer = MoveWithAck.getServerNameForRegion(region, admin, conn);
293      if (currentServer == null) {
294        LOG.warn(
295            "Could not get server for Region:" + region.getRegionNameAsString() + " moving on");
296        counter++;
297        continue;
298      } else if (server.equals(currentServer)) {
299        LOG.info(
300            "Region " + region.getRegionNameAsString() + " is already on target server=" + server);
301        counter++;
302        continue;
303      }
304      if (ack) {
305        Future<Boolean> task = moveRegionsPool
306          .submit(new MoveWithAck(conn, region, currentServer, server, movedRegions));
307        taskList.add(task);
308      } else {
309        Future<Boolean> task = moveRegionsPool
310          .submit(new MoveWithoutAck(admin, region, currentServer, server, movedRegions));
311        taskList.add(task);
312      }
313      counter++;
314    }
315
316    moveRegionsPool.shutdown();
317    long timeoutInSeconds = regionsToMove.size() * admin.getConfiguration()
318        .getLong(MOVE_WAIT_MAX_KEY, DEFAULT_MOVE_WAIT_MAX);
319    waitMoveTasksToFinish(moveRegionsPool, taskList, timeoutInSeconds);
320  }
321
322  /**
323   * Unload regions from given {@link #hostname} using ack/noAck mode and {@link #maxthreads}.In
324   * noAck mode we do not make sure that region is successfully online on the target region
325   * server,hence it is best effort.We do not unload regions to hostnames given in
326   * {@link #excludeFile}. If designatedFile is present with some contents, we will unload regions
327   * to hostnames provided in {@link #designatedFile}
328   * @return true if unloading succeeded, false otherwise
329   */
330  public boolean unload() throws InterruptedException, ExecutionException, TimeoutException {
331    deleteFile(this.filename);
332    ExecutorService unloadPool = Executors.newFixedThreadPool(1);
333    Future<Boolean> unloadTask = unloadPool.submit(() -> {
334      List<RegionInfo> movedRegions = Collections.synchronizedList(new ArrayList<>());
335      try {
336        // Get Online RegionServers
337        List<ServerName> regionServers = new ArrayList<>();
338        regionServers.addAll(admin.getRegionServers());
339        // Remove the host Region server from target Region Servers list
340        ServerName server = stripServer(regionServers, hostname, port);
341        if (server == null) {
342          LOG.info("Could not find server '{}:{}' in the set of region servers. giving up.",
343              hostname, port);
344          LOG.debug("List of region servers: {}", regionServers);
345          return false;
346        }
347        // Remove RS not present in the designated file
348        includeExcludeRegionServers(designatedFile, regionServers, true);
349
350        // Remove RS present in the exclude file
351        includeExcludeRegionServers(excludeFile, regionServers, false);
352
353        // Remove decommissioned RS
354        Set<ServerName> decommissionedRS = new HashSet<>(admin.listDecommissionedRegionServers());
355        if (CollectionUtils.isNotEmpty(decommissionedRS)) {
356          regionServers.removeIf(decommissionedRS::contains);
357          LOG.debug("Excluded RegionServers from unloading regions to because they " +
358            "are marked as decommissioned. Servers: {}", decommissionedRS);
359        }
360
361        stripMaster(regionServers);
362        if (regionServers.isEmpty()) {
363          LOG.warn("No Regions were moved - no servers available");
364          return false;
365        }
366        unloadRegions(server, regionServers, movedRegions);
367      } catch (Exception e) {
368        LOG.error("Error while unloading regions ", e);
369        return false;
370      } finally {
371        if (movedRegions != null) {
372          writeFile(filename, movedRegions);
373        }
374      }
375      return true;
376    });
377    return waitTaskToFinish(unloadPool, unloadTask, "unloading");
378  }
379
380  private void unloadRegions(ServerName server, List<ServerName> regionServers,
381      List<RegionInfo> movedRegions) throws Exception {
382    while (true) {
383      List<RegionInfo> regionsToMove = admin.getRegions(server);
384      regionsToMove.removeAll(movedRegions);
385      if (regionsToMove.isEmpty()) {
386        LOG.info("No Regions to move....Quitting now");
387        break;
388      }
389      LOG.info("Moving {} regions from {} to {} servers using {} threads .Ack Mode: {}",
390        regionsToMove.size(), this.hostname, regionServers.size(), this.maxthreads, ack);
391      final ExecutorService moveRegionsPool = Executors.newFixedThreadPool(this.maxthreads);
392      List<Future<Boolean>> taskList = new ArrayList<>();
393      int serverIndex = 0;
394      for (RegionInfo regionToMove : regionsToMove) {
395        if (ack) {
396          Future<Boolean> task = moveRegionsPool.submit(
397            new MoveWithAck(conn, regionToMove, server, regionServers.get(serverIndex),
398              movedRegions));
399          taskList.add(task);
400        } else {
401          Future<Boolean> task = moveRegionsPool.submit(
402            new MoveWithoutAck(admin, regionToMove, server, regionServers.get(serverIndex),
403              movedRegions));
404          taskList.add(task);
405        }
406        serverIndex = (serverIndex + 1) % regionServers.size();
407      }
408      moveRegionsPool.shutdown();
409      long timeoutInSeconds = regionsToMove.size() * admin.getConfiguration()
410          .getLong(MOVE_WAIT_MAX_KEY, DEFAULT_MOVE_WAIT_MAX);
411      waitMoveTasksToFinish(moveRegionsPool, taskList, timeoutInSeconds);
412    }
413  }
414
415  private boolean waitTaskToFinish(ExecutorService pool, Future<Boolean> task, String operation)
416      throws TimeoutException, InterruptedException, ExecutionException {
417    pool.shutdown();
418    try {
419      if (!pool.awaitTermination((long) this.timeout, TimeUnit.SECONDS)) {
420        LOG.warn(
421            "Timed out before finishing the " + operation + " operation. Timeout: " + this.timeout
422                + "sec");
423        pool.shutdownNow();
424      }
425    } catch (InterruptedException e) {
426      pool.shutdownNow();
427      Thread.currentThread().interrupt();
428    }
429    try {
430      return task.get(5, TimeUnit.SECONDS);
431    } catch (InterruptedException e) {
432      LOG.warn("Interrupted while " + operation + " Regions on " + this.hostname, e);
433      throw e;
434    } catch (ExecutionException e) {
435      LOG.error("Error while " + operation + " regions on RegionServer " + this.hostname, e);
436      throw e;
437    }
438  }
439
440  private void waitMoveTasksToFinish(ExecutorService moveRegionsPool,
441      List<Future<Boolean>> taskList, long timeoutInSeconds) throws Exception {
442    try {
443      if (!moveRegionsPool.awaitTermination(timeoutInSeconds, TimeUnit.SECONDS)) {
444        moveRegionsPool.shutdownNow();
445      }
446    } catch (InterruptedException e) {
447      moveRegionsPool.shutdownNow();
448      Thread.currentThread().interrupt();
449    }
450    for (Future<Boolean> future : taskList) {
451      try {
452        // if even after shutdownNow threads are stuck we wait for 5 secs max
453        if (!future.get(5, TimeUnit.SECONDS)) {
454          LOG.error("Was Not able to move region....Exiting Now");
455          throw new Exception("Could not move region Exception");
456        }
457      } catch (InterruptedException e) {
458        LOG.error("Interrupted while waiting for Thread to Complete " + e.getMessage(), e);
459        throw e;
460      } catch (ExecutionException e) {
461        boolean ignoreFailure = ignoreRegionMoveFailure(e);
462        if (ignoreFailure) {
463          LOG.debug("Ignore region move failure, it might have been split/merged.", e);
464        } else {
465          LOG.error("Got Exception From Thread While moving region {}", e.getMessage(), e);
466          throw e;
467        }
468      } catch (CancellationException e) {
469        LOG.error("Thread for moving region cancelled. Timeout for cancellation:" + timeoutInSeconds
470            + "secs", e);
471        throw e;
472      }
473    }
474  }
475
476  private boolean ignoreRegionMoveFailure(ExecutionException e) {
477    boolean ignoreFailure = false;
478    if (e.getCause() instanceof UnknownRegionException) {
479      // region does not exist anymore
480      ignoreFailure = true;
481    } else if (e.getCause() instanceof DoNotRetryRegionException
482        && e.getCause().getMessage() != null && e.getCause().getMessage()
483        .contains(AssignmentManager.UNEXPECTED_STATE_REGION + "state=SPLIT,")) {
484      // region is recently split
485      ignoreFailure = true;
486    }
487    return ignoreFailure;
488  }
489
490  private ServerName getTargetServer() throws Exception {
491    ServerName server = null;
492    int maxWaitInSeconds =
493        admin.getConfiguration().getInt(SERVERSTART_WAIT_MAX_KEY, DEFAULT_SERVERSTART_WAIT_MAX);
494    long maxWait = EnvironmentEdgeManager.currentTime() + maxWaitInSeconds * 1000;
495    while (EnvironmentEdgeManager.currentTime() < maxWait) {
496      try {
497        List<ServerName> regionServers = new ArrayList<>();
498        regionServers.addAll(admin.getRegionServers());
499        // Remove the host Region server from target Region Servers list
500        server = stripServer(regionServers, hostname, port);
501        if (server != null) {
502          break;
503        } else {
504          LOG.warn("Server " + hostname + ":" + port + " is not up yet, waiting");
505        }
506      } catch (IOException e) {
507        LOG.warn("Could not get list of region servers", e);
508      }
509      Thread.sleep(500);
510    }
511    if (server == null) {
512      LOG.error("Server " + hostname + ":" + port + " is not up. Giving up.");
513      throw new Exception("Server " + hostname + ":" + port + " to load regions not online");
514    }
515    return server;
516  }
517
518  private List<RegionInfo> readRegionsFromFile(String filename) throws IOException {
519    List<RegionInfo> regions = new ArrayList<>();
520    File f = new File(filename);
521    if (!f.exists()) {
522      return regions;
523    }
524    try (DataInputStream dis = new DataInputStream(
525        new BufferedInputStream(new FileInputStream(f)))) {
526      int numRegions = dis.readInt();
527      int index = 0;
528      while (index < numRegions) {
529        regions.add(RegionInfo.parseFromOrNull(Bytes.readByteArray(dis)));
530        index++;
531      }
532    } catch (IOException e) {
533      LOG.error("Error while reading regions from file:" + filename, e);
534      throw e;
535    }
536    return regions;
537  }
538
539  /**
540   * Write the number of regions moved in the first line followed by regions moved in subsequent
541   * lines
542   */
543  private void writeFile(String filename, List<RegionInfo> movedRegions) throws IOException {
544    try (DataOutputStream dos = new DataOutputStream(
545        new BufferedOutputStream(new FileOutputStream(filename)))) {
546      dos.writeInt(movedRegions.size());
547      for (RegionInfo region : movedRegions) {
548        Bytes.writeByteArray(dos, RegionInfo.toByteArray(region));
549      }
550    } catch (IOException e) {
551      LOG.error(
552          "ERROR: Was Not able to write regions moved to output file but moved " + movedRegions
553              .size() + " regions", e);
554      throw e;
555    }
556  }
557
558  private void deleteFile(String filename) {
559    File f = new File(filename);
560    if (f.exists()) {
561      f.delete();
562    }
563  }
564
565  /**
566   * @param filename The file should have 'host:port' per line
567   * @return List of servers from the file in format 'hostname:port'.
568   */
569  private List<String> readServersFromFile(String filename) throws IOException {
570    List<String> servers = new ArrayList<>();
571    if (filename != null) {
572      try {
573        Files.readAllLines(Paths.get(filename)).stream().map(String::trim)
574          .filter(((Predicate<String>) String::isEmpty).negate()).map(String::toLowerCase)
575          .forEach(servers::add);
576      } catch (IOException e) {
577        LOG.error("Exception while reading servers from file,", e);
578        throw e;
579      }
580    }
581    return servers;
582  }
583
584  /**
585   * Designates or excludes the servername whose hostname and port portion matches the list given
586   * in the file.
587   * Example:<br>
588   * If you want to designated RSs, suppose designatedFile has RS1, regionServers has RS1, RS2 and
589   * RS3. When we call includeExcludeRegionServers(designatedFile, regionServers, true), RS2 and
590   * RS3 are removed from regionServers list so that regions can move to only RS1.
591   * If you want to exclude RSs, suppose excludeFile has RS1, regionServers has RS1, RS2 and RS3.
592   * When we call includeExcludeRegionServers(excludeFile, servers, false), RS1 is removed from
593   * regionServers list so that regions can move to only RS2 and RS3.
594   */
595  private void includeExcludeRegionServers(String fileName, List<ServerName> regionServers,
596      boolean isInclude) throws IOException {
597    if (fileName != null) {
598      List<String> servers = readServersFromFile(fileName);
599      if (servers.isEmpty()) {
600        LOG.warn("No servers provided in the file: {}." + fileName);
601        return;
602      }
603      Iterator<ServerName> i = regionServers.iterator();
604      while (i.hasNext()) {
605        String rs = i.next().getServerName();
606        String rsPort = rs.split(ServerName.SERVERNAME_SEPARATOR)[0].toLowerCase() + ":" + rs
607          .split(ServerName.SERVERNAME_SEPARATOR)[1];
608        if (isInclude != servers.contains(rsPort)) {
609          i.remove();
610        }
611      }
612    }
613  }
614
615  /**
616   * Exclude master from list of RSs to move regions to
617   */
618  private void stripMaster(List<ServerName> regionServers) throws IOException {
619    ServerName master = admin.getClusterMetrics(EnumSet.of(Option.MASTER)).getMasterName();
620    stripServer(regionServers, master.getHostname(), master.getPort());
621  }
622
623  /**
624   * Remove the servername whose hostname and port portion matches from the passed array of servers.
625   * Returns as side-effect the servername removed.
626   * @return server removed from list of Region Servers
627   */
628  private ServerName stripServer(List<ServerName> regionServers, String hostname, int port) {
629    for (Iterator<ServerName> iter = regionServers.iterator(); iter.hasNext();) {
630      ServerName server = iter.next();
631      if (server.getAddress().getHostname().equalsIgnoreCase(hostname) &&
632        server.getAddress().getPort() == port) {
633        iter.remove();
634        return server;
635      }
636    }
637    return null;
638  }
639
640  @Override
641  protected void addOptions() {
642    this.addRequiredOptWithArg("r", "regionserverhost", "region server <hostname>|<hostname:port>");
643    this.addRequiredOptWithArg("o", "operation", "Expected: load/unload");
644    this.addOptWithArg("m", "maxthreads",
645        "Define the maximum number of threads to use to unload and reload the regions");
646    this.addOptWithArg("x", "excludefile",
647        "File with <hostname:port> per line to exclude as unload targets; default excludes only "
648            + "target host; useful for rack decommisioning.");
649    this.addOptWithArg("d","designatedfile","File with <hostname:port> per line as unload targets;"
650            + "default is all online hosts");
651    this.addOptWithArg("f", "filename",
652        "File to save regions list into unloading, or read from loading; "
653            + "default /tmp/<usernamehostname:port>");
654    this.addOptNoArg("n", "noack",
655        "Turn on No-Ack mode(default: false) which won't check if region is online on target "
656            + "RegionServer, hence best effort. This is more performant in unloading and loading "
657            + "but might lead to region being unavailable for some time till master reassigns it "
658            + "in case the move failed");
659    this.addOptWithArg("t", "timeout", "timeout in seconds after which the tool will exit "
660        + "irrespective of whether it finished or not;default Integer.MAX_VALUE");
661  }
662
663  @Override
664  protected void processOptions(CommandLine cmd) {
665    String hostname = cmd.getOptionValue("r");
666    rmbuilder = new RegionMoverBuilder(hostname);
667    if (cmd.hasOption('m')) {
668      rmbuilder.maxthreads(Integer.parseInt(cmd.getOptionValue('m')));
669    }
670    if (cmd.hasOption('n')) {
671      rmbuilder.ack(false);
672    }
673    if (cmd.hasOption('f')) {
674      rmbuilder.filename(cmd.getOptionValue('f'));
675    }
676    if (cmd.hasOption('x')) {
677      rmbuilder.excludeFile(cmd.getOptionValue('x'));
678    }
679    if (cmd.hasOption('d')) {
680      rmbuilder.designatedFile(cmd.getOptionValue('d'));
681    }
682    if (cmd.hasOption('t')) {
683      rmbuilder.timeout(Integer.parseInt(cmd.getOptionValue('t')));
684    }
685    this.loadUnload = cmd.getOptionValue("o").toLowerCase(Locale.ROOT);
686  }
687
688  @Override
689  protected int doWork() throws Exception {
690    boolean success;
691    try (RegionMover rm = rmbuilder.build()) {
692      if (loadUnload.equalsIgnoreCase("load")) {
693        success = rm.load();
694      } else if (loadUnload.equalsIgnoreCase("unload")) {
695        success = rm.unload();
696      } else {
697        printUsage();
698        success = false;
699      }
700    }
701    return (success ? 0 : 1);
702  }
703
704  public static void main(String[] args) {
705    try (RegionMover mover = new RegionMover()) {
706      mover.doStaticMain(args);
707    }
708  }
709}