joshelser commented on a change in pull request #3536:
URL: https://github.com/apache/hbase/pull/3536#discussion_r679453610



##########
File path: hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
##########
@@ -1253,6 +1253,15 @@ default boolean balancer() throws IOException {
    */
   boolean balance() throws IOException;
 
+  /**
+   * Invoke the balancer in dry run mode. Will show plan but not actually move 
any regions.
+   * Can NOT run for various reasons. Check logs.
+   *
+   * @return <code>true</code> if dry run ran, <code>false</code> otherwise.
+   * @throws IOException if a remote or network exception occurs
+   */
+  boolean dryRunBalance() throws IOException;

Review comment:
       I'm not a big fan of adding a brand new API just for the `dry-run` mode. 
What about overloading the existing `balance()` method?

##########
File path: 
hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
##########
@@ -86,6 +86,18 @@
   // We deliberately use 'localhost' so the operation will fail fast
   ServerName BOGUS_SERVER_NAME = ServerName.valueOf("localhost,1,1");
 
+  enum RunMode {
+    CHORE, REQUEST, FORCE, DRY_RUN;
+
+    boolean isForced() {
+      return this == FORCE;
+    }
+
+    boolean isDryRun() {
+      return this == DRY_RUN;
+    }

Review comment:
       IMO, these aren't adding much over `runMode == RunMode.DRY_RUN` and 
`runMode == RunMode.FORCE`.

##########
File path: 
hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterDryRunBalancer.java
##########
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.master;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.regionserver.HRegionServer;
+import org.apache.hadoop.hbase.testclassification.MasterTests;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.mockito.Mockito;
+
+@Category({ MasterTests.class, MediumTests.class})
+public class TestMasterDryRunBalancer {
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestMasterDryRunBalancer.class);
+
+  private static final HBaseTestingUtility TEST_UTIL = new 
HBaseTestingUtility();
+  private static final byte[] FAMILYNAME = Bytes.toBytes("fam");
+
+  @After
+  public void shutdown() throws Exception {
+    TEST_UTIL.shutdownMiniCluster();
+  }
+
+  @Test
+  public void testDryRunBalancer() throws Exception {
+    TEST_UTIL.startMiniCluster(2);
+
+    TableName tableName = createTable("testDryRunBalancer");
+    HMaster master = Mockito.spy(TEST_UTIL.getHBaseCluster().getMaster());
+
+    // dry run should be possible with balancer disabled
+    // disabling it will ensure the chore does not mess with our forced 
unbalance below
+    master.balanceSwitch(false);
+    assertFalse(master.isBalancerOn());
+
+    HRegionServer biasedServer = unbalance(master, tableName);
+
+    assertTrue(master.balance(LoadBalancer.RunMode.DRY_RUN));
+
+    // sanity check that we truly don't try to execute any plans
+    Mockito.verify(master, 
Mockito.never()).executeRegionPlansWithThrottling(Mockito.anyList());
+
+    // should still be unbalanced post dry run
+    assertServerContainsAllRegions(biasedServer.getServerName(), tableName);
+
+    TEST_UTIL.deleteTable(tableName);
+  }
+
+  private TableName createTable(String table) throws IOException {
+    TableName tableName = TableName.valueOf(table);
+    byte[] startKey = new byte[] { 0x00 };
+    byte[] stopKey = new byte[] { 0x7f };
+    TEST_UTIL.createTable(tableName, new byte[][] { FAMILYNAME }, 1, startKey, 
stopKey,
+      100);
+    return tableName;
+  }
+
+
+  private HRegionServer unbalance(HMaster master, TableName tableName) throws 
Exception {
+    while 
(master.getAssignmentManager().getRegionStates().getRegionsInTransitionCount() 
> 0) {
+      Thread.sleep(100);

Review comment:
       Please use the `Waiter` class to bound the total amount of time this 
test will attempt to wait (i.e. bound it to be 10-15s)

##########
File path: 
hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
##########
@@ -86,6 +86,18 @@
   // We deliberately use 'localhost' so the operation will fail fast
   ServerName BOGUS_SERVER_NAME = ServerName.valueOf("localhost,1,1");
 
+  enum RunMode {

Review comment:
       probably also want InterfaceAudience/Visibility here.

##########
File path: 
hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
##########
@@ -86,6 +86,18 @@
   // We deliberately use 'localhost' so the operation will fail fast
   ServerName BOGUS_SERVER_NAME = ServerName.valueOf("localhost,1,1");
 
+  enum RunMode {

Review comment:
       Javadoc on the enum and values, please.

##########
File path: 
hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
##########
@@ -86,6 +86,18 @@
   // We deliberately use 'localhost' so the operation will fail fast
   ServerName BOGUS_SERVER_NAME = ServerName.valueOf("localhost,1,1");
 
+  enum RunMode {
+    CHORE, REQUEST, FORCE, DRY_RUN;

Review comment:
       `FORCE` is only given by a user, right? (not something HBase does on its 
own)
   
   What about calling this `FORCED_REQUEST` (or similar) instead to reflect 
that?

##########
File path: 
hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
##########
@@ -1809,6 +1809,10 @@ public boolean balance(boolean force) throws IOException 
{
 
       List<RegionPlan> plans = this.balancer.balanceCluster(assignments);
 
+      if (runMode.isDryRun()) {
+        return true;

Review comment:
       Maybe outside of the scope of this work, but it would be great, IMO, to 
pass back details on what the balancer actually did through the `balancer` RPC. 
Simple output could just be "moved N regions". A verbose option could actually 
enumerate the plan changes. That'd be way nicer than having to hunt-peck in the 
master log after you make this call.

##########
File path: hbase-shell/src/main/ruby/shell/commands/dry_run_balancer.rb
##########
@@ -0,0 +1,43 @@
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+module Shell
+  module Commands
+    class DryRunBalancer < Command
+      def help
+        return <<-EOF
+Trigger a dry run of the cluster balancer. This will execute the balancer 
logic,
+without actually moving any regions in the cluster. The output of this is 
useful
+for debugging balancer changes, or even adjustments to balancer weights. You 
are
+effectively able to preview balancer changes to the cluster before actually
+executing them.
+This will NOT run if regions in transition. This also will NOT
+run if the balancer is enabled (as to make sure this logic does not clash with
+the actual balancer).
+EOF
+      end
+
+      def command()
+        did_balancer_run = !!admin.dryRunBalancer()
+        formatter.row([did_balancer_run.to_s])
+        did_balancer_run
+      end

Review comment:
       Going off of what I suggested above with a single `Admin#balance()` 
method which can supply arguments, I think it'd be great to just extend the 
existing `balancer` command and let the user pass in a dry-run flag.

##########
File path: 
hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterDryRunBalancer.java
##########
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.master;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.regionserver.HRegionServer;
+import org.apache.hadoop.hbase.testclassification.MasterTests;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.mockito.Mockito;
+
+@Category({ MasterTests.class, MediumTests.class})
+public class TestMasterDryRunBalancer {
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestMasterDryRunBalancer.class);
+
+  private static final HBaseTestingUtility TEST_UTIL = new 
HBaseTestingUtility();
+  private static final byte[] FAMILYNAME = Bytes.toBytes("fam");
+
+  @After
+  public void shutdown() throws Exception {
+    TEST_UTIL.shutdownMiniCluster();
+  }
+
+  @Test
+  public void testDryRunBalancer() throws Exception {
+    TEST_UTIL.startMiniCluster(2);
+
+    TableName tableName = createTable("testDryRunBalancer");
+    HMaster master = Mockito.spy(TEST_UTIL.getHBaseCluster().getMaster());
+
+    // dry run should be possible with balancer disabled
+    // disabling it will ensure the chore does not mess with our forced 
unbalance below
+    master.balanceSwitch(false);
+    assertFalse(master.isBalancerOn());
+
+    HRegionServer biasedServer = unbalance(master, tableName);
+
+    assertTrue(master.balance(LoadBalancer.RunMode.DRY_RUN));
+
+    // sanity check that we truly don't try to execute any plans
+    Mockito.verify(master, 
Mockito.never()).executeRegionPlansWithThrottling(Mockito.anyList());
+
+    // should still be unbalanced post dry run
+    assertServerContainsAllRegions(biasedServer.getServerName(), tableName);
+
+    TEST_UTIL.deleteTable(tableName);
+  }
+
+  private TableName createTable(String table) throws IOException {
+    TableName tableName = TableName.valueOf(table);
+    byte[] startKey = new byte[] { 0x00 };
+    byte[] stopKey = new byte[] { 0x7f };
+    TEST_UTIL.createTable(tableName, new byte[][] { FAMILYNAME }, 1, startKey, 
stopKey,
+      100);

Review comment:
       Doesn't look like you actually care what the start/stop key are. You 
could use `HTU#createMultiRegionTable(TableName, byte[], int)` instead.

##########
File path: 
hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
##########
@@ -676,7 +676,16 @@ public BalanceResponse balance(RpcController controller,
       BalanceRequest request) throws ServiceException {
     try {
       return BalanceResponse.newBuilder().setBalancerRan(master.balance(
-        request.hasForce()? request.getForce(): false)).build();
+        request.hasForce() && request.getForce() ? LoadBalancer.RunMode.FORCE 
: LoadBalancer.RunMode.REQUEST)).build();

Review comment:
       This is getting a little complex. Could you pull getting the `RunMode` 
out of this line? (just do it in on its own line, please).

##########
File path: 
hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterDryRunBalancer.java
##########
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.hbase.master;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.regionserver.HRegionServer;
+import org.apache.hadoop.hbase.testclassification.MasterTests;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.mockito.Mockito;
+
+@Category({ MasterTests.class, MediumTests.class})
+public class TestMasterDryRunBalancer {
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestMasterDryRunBalancer.class);
+
+  private static final HBaseTestingUtility TEST_UTIL = new 
HBaseTestingUtility();
+  private static final byte[] FAMILYNAME = Bytes.toBytes("fam");
+
+  @After
+  public void shutdown() throws Exception {
+    TEST_UTIL.shutdownMiniCluster();
+  }
+
+  @Test
+  public void testDryRunBalancer() throws Exception {
+    TEST_UTIL.startMiniCluster(2);
+
+    TableName tableName = createTable("testDryRunBalancer");
+    HMaster master = Mockito.spy(TEST_UTIL.getHBaseCluster().getMaster());
+
+    // dry run should be possible with balancer disabled
+    // disabling it will ensure the chore does not mess with our forced 
unbalance below
+    master.balanceSwitch(false);
+    assertFalse(master.isBalancerOn());
+
+    HRegionServer biasedServer = unbalance(master, tableName);
+
+    assertTrue(master.balance(LoadBalancer.RunMode.DRY_RUN));
+
+    // sanity check that we truly don't try to execute any plans
+    Mockito.verify(master, 
Mockito.never()).executeRegionPlansWithThrottling(Mockito.anyList());
+
+    // should still be unbalanced post dry run
+    assertServerContainsAllRegions(biasedServer.getServerName(), tableName);
+
+    TEST_UTIL.deleteTable(tableName);
+  }
+
+  private TableName createTable(String table) throws IOException {
+    TableName tableName = TableName.valueOf(table);
+    byte[] startKey = new byte[] { 0x00 };
+    byte[] stopKey = new byte[] { 0x7f };
+    TEST_UTIL.createTable(tableName, new byte[][] { FAMILYNAME }, 1, startKey, 
stopKey,
+      100);
+    return tableName;
+  }
+
+
+  private HRegionServer unbalance(HMaster master, TableName tableName) throws 
Exception {
+    while 
(master.getAssignmentManager().getRegionStates().getRegionsInTransitionCount() 
> 0) {
+      Thread.sleep(100);
+    }
+    HRegionServer biasedServer = 
TEST_UTIL.getMiniHBaseCluster().getRegionServer(0);
+
+    for (RegionInfo regionInfo : TEST_UTIL.getAdmin().getRegions(tableName)) {
+      master.move(regionInfo.getEncodedNameAsBytes(),
+        Bytes.toBytes(biasedServer.getServerName().getServerName()));
+    }
+
+    while 
(master.getAssignmentManager().getRegionStates().getRegionsInTransitionCount() 
> 0) {
+      Thread.sleep(100);

Review comment:
       Waiter here too, please.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@hbase.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Reply via email to