diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/RegionMover.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/RegionMover.java index 742f25abe6cb..693bfc5adda2 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/RegionMover.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/RegionMover.java @@ -101,6 +101,7 @@ public class RegionMover extends AbstractHBaseTool implements Closeable { private String hostname; private String filename; private String excludeFile; + private String designatedFile; private int port; private Connection conn; private Admin admin; @@ -109,6 +110,7 @@ private RegionMover(RegionMoverBuilder builder) throws IOException { this.hostname = builder.hostname; this.filename = builder.filename; this.excludeFile = builder.excludeFile; + this.designatedFile = builder.designatedFile; this.maxthreads = builder.maxthreads; this.ack = builder.ack; this.port = builder.port; @@ -130,7 +132,8 @@ public void close() { /** * Builder for Region mover. Use the {@link #build()} method to create RegionMover object. Has * {@link #filename(String)}, {@link #excludeFile(String)}, {@link #maxthreads(int)}, - * {@link #ack(boolean)}, {@link #timeout(int)} methods to set the corresponding options + * {@link #ack(boolean)}, {@link #timeout(int)}, {@link #designatedFile(String)} methods to set + * the corresponding options. */ public static class RegionMoverBuilder { private boolean ack = true; @@ -139,6 +142,7 @@ public static class RegionMoverBuilder { private String hostname; private String filename; private String excludeFile = null; + private String designatedFile = null; private String defaultDir = System.getProperty("java.io.tmpdir"); @VisibleForTesting final int port; @@ -205,6 +209,18 @@ public RegionMoverBuilder excludeFile(String excludefile) { return this; } + /** + * Set the designated file. Designated file contains hostnames where region moves. Designated + * file should have 'host:port' per line. Port is mandatory here as we can have many RS running + * on a single host. + * @param designatedFile The designated file + * @return RegionMoverBuilder object + */ + public RegionMoverBuilder designatedFile(String designatedFile) { + this.designatedFile = designatedFile; + return this; + } + /** * Set ack/noAck mode. *

@@ -413,7 +429,8 @@ private void loadRegions(List regionsToMove) * Unload regions from given {@link #hostname} using ack/noAck mode and {@link #maxthreads}.In * noAck mode we do not make sure that region is successfully online on the target region * server,hence it is best effort.We do not unload regions to hostnames given in - * {@link #excludeFile}. + * {@link #excludeFile}. If designatedFile is present with some contents, we will unload regions + * to hostnames provided in {@link #designatedFile} * @return true if unloading succeeded, false otherwise */ public boolean unload() throws InterruptedException, ExecutionException, TimeoutException { @@ -435,8 +452,11 @@ public boolean unload() throws InterruptedException, ExecutionException, Timeout LOG.debug("List of region servers: {}", regionServers); return false; } + // Remove RS not present in the designated file + includeExcludeRegionServers(designatedFile, regionServers, true); + // Remove RS present in the exclude file - stripExcludes(regionServers); + includeExcludeRegionServers(excludeFile, regionServers, false); // Remove decommissioned RS Set decommissionedRS = new HashSet<>(admin.listDecommissionedRegionServers()); @@ -653,41 +673,52 @@ private void deleteFile(String filename) { } /** - * @return List of servers from the exclude file in format 'hostname:port'. + * @param filename The file should have 'host:port' per line + * @return List of servers from the file in format 'hostname:port'. */ - private List readExcludes(String excludeFile) throws IOException { - List excludeServers = new ArrayList<>(); - if (excludeFile == null) { - return excludeServers; - } else { + private List readServersFromFile(String filename) throws IOException { + List servers = new ArrayList<>(); + if (filename != null) { try { - Files.readAllLines(Paths.get(excludeFile)).stream().map(String::trim) - .filter(((Predicate) String::isEmpty).negate()).map(String::toLowerCase) - .forEach(excludeServers::add); + Files.readAllLines(Paths.get(filename)).stream().map(String::trim) + .filter(((Predicate) String::isEmpty).negate()).map(String::toLowerCase) + .forEach(servers::add); } catch (IOException e) { - LOG.warn("Exception while reading excludes file, continuing anyways", e); + LOG.error("Exception while reading servers from file,", e); + throw e; } - return excludeServers; } + return servers; } /** - * Excludes the servername whose hostname and port portion matches the list given in exclude file + * Designates or excludes the servername whose hostname and port portion matches the list given + * in the file. + * Example:
+ * If you want to designated RSs, suppose designatedFile has RS1, regionServers has RS1, RS2 and + * RS3. When we call includeExcludeRegionServers(designatedFile, regionServers, true), RS2 and + * RS3 are removed from regionServers list so that regions can move to only RS1. + * If you want to exclude RSs, suppose excludeFile has RS1, regionServers has RS1, RS2 and RS3. + * When we call includeExcludeRegionServers(excludeFile, servers, false), RS1 is removed from + * regionServers list so that regions can move to only RS2 and RS3. */ - private void stripExcludes(List regionServers) throws IOException { - if (excludeFile != null) { - List excludes = readExcludes(excludeFile); + private void includeExcludeRegionServers(String fileName, List regionServers, + boolean isInclude) throws IOException { + if (fileName != null) { + List servers = readServersFromFile(fileName); + if (servers.isEmpty()) { + LOG.warn("No servers provided in the file: {}." + fileName); + return; + } Iterator i = regionServers.iterator(); while (i.hasNext()) { String rs = i.next().getServerName(); String rsPort = rs.split(ServerName.SERVERNAME_SEPARATOR)[0].toLowerCase() + ":" + rs - .split(ServerName.SERVERNAME_SEPARATOR)[1]; - if (excludes.contains(rsPort)) { + .split(ServerName.SERVERNAME_SEPARATOR)[1]; + if (isInclude != servers.contains(rsPort)) { i.remove(); } } - LOG.info("Valid Region server targets are:" + regionServers.toString()); - LOG.info("Excluded Servers are" + excludes.toString()); } } @@ -773,6 +804,8 @@ protected void addOptions() { this.addOptWithArg("x", "excludefile", "File with per line to exclude as unload targets; default excludes only " + "target host; useful for rack decommisioning."); + this.addOptWithArg("d","designatedfile","File with per line as unload targets;" + + "default is all online hosts"); this.addOptWithArg("f", "filename", "File to save regions list into unloading, or read from loading; " + "default /tmp/"); @@ -801,6 +834,9 @@ protected void processOptions(CommandLine cmd) { if (cmd.hasOption('x')) { rmbuilder.excludeFile(cmd.getOptionValue('x')); } + if (cmd.hasOption('d')) { + rmbuilder.designatedFile(cmd.getOptionValue('d')); + } if (cmd.hasOption('t')) { rmbuilder.timeout(Integer.parseInt(cmd.getOptionValue('t'))); } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestRegionMover.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestRegionMover.java index 4d18f4bb6b7f..b7e947cca4db 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestRegionMover.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestRegionMover.java @@ -180,6 +180,85 @@ public void testExclude() throws Exception { } } + @Test + public void testDesignatedFile() throws Exception{ + MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster(); + File designatedFile = new File(TEST_UTIL.getDataTestDir().toUri().getPath(), + "designated_file"); + HRegionServer designatedServer = cluster.getRegionServer(0); + try(FileWriter fos = new FileWriter(designatedFile)) { + String designatedHostname = designatedServer.getServerName().getHostname(); + int designatedServerPort = designatedServer.getServerName().getPort(); + String excludeServerName = designatedHostname + ":" + designatedServerPort; + fos.write(excludeServerName); + } + int regionsInDesignatedServer = designatedServer.getNumberOfOnlineRegions(); + HRegionServer regionServer = cluster.getRegionServer(1); + String rsName = regionServer.getServerName().getHostname(); + int port = regionServer.getServerName().getPort(); + String rs = rsName + ":" + port; + int regionsInRegionServer = regionServer.getNumberOfOnlineRegions(); + RegionMoverBuilder rmBuilder = new RegionMoverBuilder(rs, TEST_UTIL.getConfiguration()) + .designatedFile(designatedFile.getCanonicalPath()); + try (RegionMover rm = rmBuilder.build()) { + LOG.debug("Unloading {} regions", rs); + rm.unload(); + assertEquals(0, regionServer.getNumberOfOnlineRegions()); + assertEquals(regionsInDesignatedServer + regionsInRegionServer, + designatedServer.getNumberOfOnlineRegions()); + LOG.debug("Before:{} After:{}", regionsInDesignatedServer, + designatedServer.getNumberOfOnlineRegions()); + } + } + + @Test + public void testExcludeAndDesignated() throws Exception{ + MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster(); + // create designated file + File designatedFile = new File(TEST_UTIL.getDataTestDir().toUri().getPath(), + "designated_file"); + HRegionServer designatedServer = cluster.getRegionServer(0); + try(FileWriter fos = new FileWriter(designatedFile)) { + String designatedHostname = designatedServer.getServerName().getHostname(); + int designatedServerPort = designatedServer.getServerName().getPort(); + String excludeServerName = designatedHostname + ":" + designatedServerPort; + fos.write(excludeServerName); + } + int regionsInDesignatedServer = designatedServer.getNumberOfOnlineRegions(); + // create exclude file + File excludeFile = new File(TEST_UTIL.getDataTestDir().toUri().getPath(), "exclude_file"); + HRegionServer excludeServer = cluster.getRegionServer(1); + try(FileWriter fos = new FileWriter(excludeFile)) { + String excludeHostname = excludeServer.getServerName().getHostname(); + int excludeServerPort = excludeServer.getServerName().getPort(); + String excludeServerName = excludeHostname + ":" + excludeServerPort; + fos.write(excludeServerName); + } + int regionsInExcludeServer = excludeServer.getNumberOfOnlineRegions(); + + HRegionServer targetRegionServer = cluster.getRegionServer(2); + String rsName = targetRegionServer.getServerName().getHostname(); + int port = targetRegionServer.getServerName().getPort(); + String rs = rsName + ":" + port; + int regionsInTargetRegionServer = targetRegionServer.getNumberOfOnlineRegions(); + + RegionMoverBuilder rmBuilder = new RegionMoverBuilder(rs, TEST_UTIL.getConfiguration()) + .designatedFile(designatedFile.getCanonicalPath()) + .excludeFile(excludeFile.getCanonicalPath()); + try (RegionMover rm = rmBuilder.build()) { + LOG.debug("Unloading {}", rs); + rm.unload(); + assertEquals(0, targetRegionServer.getNumberOfOnlineRegions()); + assertEquals(regionsInDesignatedServer + regionsInTargetRegionServer, + designatedServer.getNumberOfOnlineRegions()); + LOG.debug("DesignatedServer Before:{} After:{}", regionsInDesignatedServer, + designatedServer.getNumberOfOnlineRegions()); + assertEquals(regionsInExcludeServer, excludeServer.getNumberOfOnlineRegions()); + LOG.debug("ExcludeServer Before:{} After:{}", regionsInExcludeServer, + excludeServer.getNumberOfOnlineRegions()); + } + } + @Test public void testRegionServerPort() { MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();