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