This is an automated email from the ASF dual-hosted git repository.

jxue pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/helix.git


The following commit(s) were added to refs/heads/master by this push:
     new 858038a0d add type and reason to cluster config (#2006)
858038a0d is described below

commit 858038a0d4393109f3dfbb63c2f442b0f95fa50e
Author: xyuanlu <[email protected]>
AuthorDate: Tue Apr 5 18:34:49 2022 -0700

    add type and reason to cluster config (#2006)
    
    Add type and reason for batch disable/enable instance
---
 .../src/main/java/org/apache/helix/HelixAdmin.java | 18 ++++++-
 .../rebalancer/util/DelayedRebalanceUtil.java      |  5 +-
 .../org/apache/helix/manager/zk/ZKHelixAdmin.java  | 60 ++++++++++++++--------
 .../java/org/apache/helix/model/ClusterConfig.java | 34 +++++++++++-
 .../apache/helix/util/InstanceValidationUtil.java  | 42 ++++++++++++++-
 .../integration/TestBatchEnableInstances.java      | 48 ++++++++++++++---
 .../java/org/apache/helix/mock/MockHelixAdmin.java | 43 ++++++++++++++--
 .../server/resources/helix/InstancesAccessor.java  | 13 ++++-
 .../helix/rest/server/TestInstancesAccessor.java   | 42 +++++++++------
 9 files changed, 251 insertions(+), 54 deletions(-)

diff --git a/helix-core/src/main/java/org/apache/helix/HelixAdmin.java 
b/helix-core/src/main/java/org/apache/helix/HelixAdmin.java
index 32158d541..837cd2cb0 100644
--- a/helix-core/src/main/java/org/apache/helix/HelixAdmin.java
+++ b/helix-core/src/main/java/org/apache/helix/HelixAdmin.java
@@ -285,8 +285,10 @@ public interface HelixAdmin {
    * @param clusterName
    * @param instanceName
    * @param enabled
+   * @param disabledType disabledType for disable operation. It is ignored 
when enabled is true.
+   *                     Existing disabledType will be over write if instance 
is in disabled state.
    * @param reason set additional string description on why the instance is 
disabled when
-   *          <code>enabled</code> is false.
+   *          <code>enabled</code> is false. Existing disabled reason will be 
over write if instance is in disabled state.
    */
   void enableInstance(String clusterName, String instanceName, boolean enabled,
       InstanceConstants.InstanceDisabledType disabledType, String reason);
@@ -300,6 +302,20 @@ public interface HelixAdmin {
    */
   void enableInstance(String clusterName, List<String> instances, boolean 
enabled);
 
+  /**
+   * Batch enable/disable instances in a cluster
+   * By default, all the instances are enabled
+   * @param clusterName
+   * @param instances
+   * @param enabled
+   * @param disabledType disabledType for disable operation. It is ignored 
when enabled is true.
+   *                     Existing disabledType will be over write if instance 
is in disabled state.
+   * @param reason human readable string explaining disabled reason. Ignored 
when enabled is true.
+   *               Existing disabled reason will be over write if instance is 
in disabled state.
+   */
+  void enableInstance(String clusterName, List<String> instances, boolean 
enabled,
+      InstanceConstants.InstanceDisabledType disabledType, String reason);
+
   /**
    * Disable or enable a resource
    * @param clusterName
diff --git 
a/helix-core/src/main/java/org/apache/helix/controller/rebalancer/util/DelayedRebalanceUtil.java
 
b/helix-core/src/main/java/org/apache/helix/controller/rebalancer/util/DelayedRebalanceUtil.java
index 8de1f4385..43fcf4c5a 100644
--- 
a/helix-core/src/main/java/org/apache/helix/controller/rebalancer/util/DelayedRebalanceUtil.java
+++ 
b/helix-core/src/main/java/org/apache/helix/controller/rebalancer/util/DelayedRebalanceUtil.java
@@ -31,6 +31,7 @@ import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.IdealState;
 import org.apache.helix.model.InstanceConfig;
 import org.apache.helix.model.ResourceConfig;
+import org.apache.helix.util.ConfigStringUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -142,7 +143,9 @@ public class DelayedRebalanceUtil {
       if (clusterConfig.getDisabledInstances() != null && 
clusterConfig.getDisabledInstances()
           .containsKey(instance)) {
         // Update batch disable time
-        long batchDisableTime = 
Long.parseLong(clusterConfig.getDisabledInstances().get(instance));
+        long batchDisableTime = Long.parseLong(ConfigStringUtil
+            
.parseConcatenatedConfig(clusterConfig.getDisabledInstances().get(instance))
+            
.get(ClusterConfig.ClusterConfigProperty.HELIX_ENABLED_DISABLE_TIMESTAMP.toString()));
         if (disabledTime == -1 || disabledTime > batchDisableTime) {
           disabledTime = batchDisableTime;
         }
diff --git 
a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java 
b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
index a3bcfc45c..0d05d05b2 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixAdmin.java
@@ -86,6 +86,7 @@ import org.apache.helix.model.ResourceConfig;
 import org.apache.helix.model.StateModelDefinition;
 import org.apache.helix.msdcommon.exception.InvalidRoutingDataException;
 import org.apache.helix.tools.DefaultIdealStateCalculator;
+import org.apache.helix.util.ConfigStringUtil;
 import org.apache.helix.util.HelixUtil;
 import org.apache.helix.util.RebalanceUtil;
 import org.apache.helix.zookeeper.api.client.HelixZkClient;
@@ -351,28 +352,33 @@ public class ZKHelixAdmin implements HelixAdmin {
     logger.info("{} instance {} in cluster {}.", enabled ? "Enable" : 
"Disable", instanceName,
         clusterName);
     BaseDataAccessor<ZNRecord> baseAccessor = new 
ZkBaseDataAccessor<>(_zkClient);
+    // Eventually we will have all instances' enable/disable information in 
clusterConfig. Now we
+    // update both instanceConfig and clusterConfig in transition period.
     enableSingleInstance(clusterName, instanceName, enabled, baseAccessor, 
disabledType, reason);
-    // TODO: Reenable this after storage node bug fixed.
-    // enableBatchInstances(clusterName, 
Collections.singletonList(instanceName), enabled, baseAccessor);
+    enableBatchInstances(clusterName, Collections.singletonList(instanceName), 
enabled,
+        baseAccessor, disabledType, reason);
 
   }
 
   @Override
-  public void enableInstance(String clusterName, List<String> instances, 
boolean enabled) {
-    // TODO: Considering adding another batched API with  disabled type and 
reason.
-    // TODO: Reenable this after storage node bug fixed.
-    if (true) {
-      throw new HelixException("Current batch enable/disable instances are 
temporarily disabled!");
-    }
+  public void enableInstance(String clusterName, List<String> instances, 
boolean enabled,
+      InstanceConstants.InstanceDisabledType disabledType, String reason) {
     logger.info("Batch {} instances {} in cluster {}.", enabled ? "enable" : 
"disable",
         HelixUtil.serializeByComma(instances), clusterName);
     BaseDataAccessor<ZNRecord> baseAccessor = new 
ZkBaseDataAccessor<>(_zkClient);
+    // Eventually we will have all instances' enable/disable information in 
clusterConfig. Now we
+    // update both instanceConfig and clusterConfig in transition period.
     if (enabled) {
       for (String instance : instances) {
-        enableSingleInstance(clusterName, instance, enabled, baseAccessor, 
null, null);
+        enableSingleInstance(clusterName, instance, enabled, baseAccessor, 
disabledType, reason);
       }
     }
-    enableBatchInstances(clusterName, instances, enabled, baseAccessor);
+    enableBatchInstances(clusterName, instances, enabled, baseAccessor, 
disabledType, reason);
+  }
+
+  @Override
+  public void enableInstance(String clusterName, List<String> instances, 
boolean enabled) {
+    enableInstance(clusterName, instances, enabled, null, null);
   }
 
   @Override
@@ -1889,6 +1895,8 @@ public class ZKHelixAdmin implements HelixAdmin {
         InstanceConfig config = new InstanceConfig(currentData);
         config.setInstanceEnabled(enabled);
         if (!enabled) {
+          // new disabled type and reason will over write existing ones.
+          config.resetInstanceDisabledTypeAndReason();
           if (reason != null) {
             config.setInstanceDisabledReason(reason);
           }
@@ -1901,13 +1909,10 @@ public class ZKHelixAdmin implements HelixAdmin {
     }, AccessOption.PERSISTENT);
   }
 
+  // TODO: Add history ZNode for all batched enabling/disabling histories with 
metadata.
   private void enableBatchInstances(final String clusterName, final 
List<String> instances,
-      final boolean enabled, BaseDataAccessor<ZNRecord> baseAccessor) {
-    // TODO : Due to Espresso storage node depends on map field. Current 
disable the feature now
-    // include tests.
-    if (true) {
-      throw new HelixException("Current batch enable/disable instances are 
temporarily disabled!");
-    }
+      final boolean enabled, BaseDataAccessor<ZNRecord> baseAccessor,
+      InstanceConstants.InstanceDisabledType disabledType, String reason) {
 
     String path = PropertyPathBuilder.clusterConfig(clusterName);
 
@@ -1927,14 +1932,14 @@ public class ZKHelixAdmin implements HelixAdmin {
         if (clusterConfig.getDisabledInstances() != null) {
           disabledInstances.putAll(clusterConfig.getDisabledInstances());
         }
-
         if (enabled) {
           disabledInstances.keySet().removeAll(instances);
         } else {
           for (String disabledInstance : instances) {
-            if (!disabledInstances.containsKey(disabledInstance)) {
-              disabledInstances.put(disabledInstance, 
String.valueOf(System.currentTimeMillis()));
-            }
+            // We allow user to override disabledType and reason for an 
already disabled instance.
+            // TODO: update the history ZNode
+            disabledInstances
+                .put(disabledInstance, 
assembleInstanceBatchedDisabledInfo(disabledType, reason));
           }
         }
         clusterConfig.setDisabledInstances(disabledInstances);
@@ -1944,6 +1949,21 @@ public class ZKHelixAdmin implements HelixAdmin {
     }, AccessOption.PERSISTENT);
   }
 
+  public static String assembleInstanceBatchedDisabledInfo(
+      InstanceConstants.InstanceDisabledType disabledType, String reason) {
+    Map<String, String> disableInfo = new TreeMap<>();
+    
disableInfo.put(ClusterConfig.ClusterConfigProperty.HELIX_ENABLED_DISABLE_TIMESTAMP.toString(),
+        String.valueOf(System.currentTimeMillis()));
+    if (disabledType != null) {
+      
disableInfo.put(ClusterConfig.ClusterConfigProperty.HELIX_DISABLED_TYPE.toString(),
+          disabledType.toString());
+    }
+    if (reason != null) {
+      
disableInfo.put(ClusterConfig.ClusterConfigProperty.HELIX_DISABLED_REASON.toString(),
 reason);
+    }
+    return ConfigStringUtil.concatenateMapping(disableInfo);
+  }
+
   @Override
   public Map<String, String> getBatchDisabledInstances(String clusterName) {
     ConfigAccessor accessor = new ConfigAccessor(_zkClient);
diff --git a/helix-core/src/main/java/org/apache/helix/model/ClusterConfig.java 
b/helix-core/src/main/java/org/apache/helix/model/ClusterConfig.java
index 68c1d4ddd..72bc5dcb5 100644
--- a/helix-core/src/main/java/org/apache/helix/model/ClusterConfig.java
+++ b/helix-core/src/main/java/org/apache/helix/model/ClusterConfig.java
@@ -33,6 +33,8 @@ import org.apache.helix.HelixProperty;
 import org.apache.helix.api.config.HelixConfigProperty;
 import org.apache.helix.api.config.StateTransitionThrottleConfig;
 import org.apache.helix.api.config.StateTransitionTimeoutConfig;
+import org.apache.helix.constants.InstanceConstants;
+import org.apache.helix.util.ConfigStringUtil;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 
 /**
@@ -134,7 +136,13 @@ public class ClusterConfig extends HelixProperty {
     // offline for more than this specified time period, and users call purge 
participant API,
     // then the node will be removed.
     // The unit is milliseconds.
-    OFFLINE_DURATION_FOR_PURGE_MS
+    OFFLINE_DURATION_FOR_PURGE_MS,
+
+    // The following 3 keywords are for metadata in batch disabled instance
+    HELIX_ENABLED_DISABLE_TIMESTAMP,
+    HELIX_DISABLED_REASON,
+    // disabled type should be a enum of 
org.apache.helix.constants.InstanceConstants.InstanceDisabledType
+    HELIX_DISABLED_TYPE
   }
 
   public enum GlobalRebalancePreferenceKey {
@@ -1045,4 +1053,28 @@ public class ClusterConfig extends HelixProperty {
   public String getClusterName() {
     return _record.getId();
   }
+
+  public String getPlainInstanceHelixDisabledType(String instanceName) {
+    return 
ConfigStringUtil.parseConcatenatedConfig(getDisabledInstances().get(instanceName))
+        .get(ClusterConfigProperty.HELIX_DISABLED_TYPE.toString());
+  }
+
+  public String getInstanceHelixDisabledType(String instanceName) {
+    if (!getDisabledInstances().containsKey(instanceName)) {
+      return InstanceConstants.INSTANCE_NOT_DISABLED;
+    }
+    return 
ConfigStringUtil.parseConcatenatedConfig(getDisabledInstances().get(instanceName))
+        .getOrDefault(ClusterConfigProperty.HELIX_DISABLED_TYPE.toString(),
+            
InstanceConstants.InstanceDisabledType.DEFAULT_INSTANCE_DISABLE_TYPE.toString());
+  }
+
+  public String getInstanceHelixDisabledReason(String instanceName) {
+    return 
ConfigStringUtil.parseConcatenatedConfig(getDisabledInstances().get(instanceName))
+        .get(ClusterConfigProperty.HELIX_DISABLED_REASON.toString());
+  }
+
+  public String getInstanceHelixDisabledTimeStamp(String instanceName) {
+    return 
ConfigStringUtil.parseConcatenatedConfig(getDisabledInstances().get(instanceName))
+        .get(ClusterConfigProperty.HELIX_ENABLED_DISABLE_TIMESTAMP.toString());
+  }
 }
diff --git 
a/helix-core/src/main/java/org/apache/helix/util/InstanceValidationUtil.java 
b/helix-core/src/main/java/org/apache/helix/util/InstanceValidationUtil.java
index 7c6383195..48ff86b95 100644
--- a/helix-core/src/main/java/org/apache/helix/util/InstanceValidationUtil.java
+++ b/helix-core/src/main/java/org/apache/helix/util/InstanceValidationUtil.java
@@ -31,6 +31,7 @@ import org.apache.helix.HelixDataAccessor;
 import org.apache.helix.HelixDefinedState;
 import org.apache.helix.HelixException;
 import org.apache.helix.PropertyKey;
+import org.apache.helix.constants.InstanceConstants;
 import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.CurrentState;
 import org.apache.helix.model.ExternalView;
@@ -78,10 +79,46 @@ public class InstanceValidationUtil {
       throw new HelixException("InstanceConfig or ClusterConfig is NULL");
     }
 
+    return isInstanceEnabled(instanceConfig, clusterConfig);
+
+  }
+
+  public static String getInstanceHelixDisabledType(HelixDataAccessor 
dataAccessor,
+      String instanceName) {
+    PropertyKey.Builder propertyKeyBuilder = dataAccessor.keyBuilder();
+    ClusterConfig clusterConfig = 
dataAccessor.getProperty(propertyKeyBuilder.clusterConfig());
+    if (clusterConfig == null) {
+      throw new HelixException("ClusterConfig is NULL");
+    }
+
+    String instanceDisabledTypeBatchedMode =
+        clusterConfig.getPlainInstanceHelixDisabledType(instanceName);
+    if (instanceDisabledTypeBatchedMode != null) {
+      return 
InstanceConstants.InstanceDisabledType.valueOf(instanceDisabledTypeBatchedMode)
+          .toString();
+    }
+    // TODO deprecate reading instance level config once migrated the enable 
status to cluster config only
+    InstanceConfig instanceConfig =
+        
dataAccessor.getProperty(propertyKeyBuilder.instanceConfig(instanceName));
+    if (instanceConfig == null) {
+      throw new HelixException("InstanceConfig is NULL");
+    }
+
+    if (isInstanceEnabled(instanceConfig, clusterConfig)) {
+      return InstanceConstants.INSTANCE_NOT_DISABLED;
+    }
+    // It is possible that an instance is set disabled in cluster config but 
not in instance config.
+    // This instance is considered disabled. We should return a default 
disabled type.
+    return instanceConfig.getInstanceEnabled()
+        ? 
InstanceConstants.InstanceDisabledType.DEFAULT_INSTANCE_DISABLE_TYPE.toString()
+        : instanceConfig.getInstanceDisabledType();
+  }
+
+  private  static boolean isInstanceEnabled(InstanceConfig instanceConfig, 
ClusterConfig clusterConfig) {
     boolean enabledInInstanceConfig = instanceConfig.getInstanceEnabled();
     Map<String, String> disabledInstances = 
clusterConfig.getDisabledInstances();
     boolean enabledInClusterConfig =
-        disabledInstances == null || 
!disabledInstances.keySet().contains(instanceName);
+        disabledInstances == null || 
!disabledInstances.keySet().contains(instanceConfig.getInstanceName());
     return enabledInClusterConfig && enabledInInstanceConfig;
   }
 
@@ -92,7 +129,8 @@ public class InstanceValidationUtil {
    * @return
    */
   public static boolean isAlive(HelixDataAccessor dataAccessor, String 
instanceName) {
-    LiveInstance liveInstance = 
dataAccessor.getProperty(dataAccessor.keyBuilder().liveInstance(instanceName));
+    LiveInstance liveInstance =
+        
dataAccessor.getProperty(dataAccessor.keyBuilder().liveInstance(instanceName));
     return liveInstance != null;
   }
 
diff --git 
a/helix-core/src/test/java/org/apache/helix/integration/TestBatchEnableInstances.java
 
b/helix-core/src/test/java/org/apache/helix/integration/TestBatchEnableInstances.java
index b7b060064..6dfd0037f 100644
--- 
a/helix-core/src/test/java/org/apache/helix/integration/TestBatchEnableInstances.java
+++ 
b/helix-core/src/test/java/org/apache/helix/integration/TestBatchEnableInstances.java
@@ -23,9 +23,15 @@ import java.util.Arrays;
 import java.util.Map;
 
 import org.apache.helix.ConfigAccessor;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.constants.InstanceConstants;
 import org.apache.helix.integration.task.TaskTestBase;
 import org.apache.helix.integration.task.WorkflowGenerator;
+import org.apache.helix.manager.zk.ZKHelixDataAccessor;
+import org.apache.helix.manager.zk.ZkBaseDataAccessor;
+import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.ExternalView;
+import org.apache.helix.util.InstanceValidationUtil;
 import org.testng.Assert;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -43,7 +49,7 @@ public class TestBatchEnableInstances extends TaskTestBase {
     _accessor = new ConfigAccessor(_gZkClient);
   }
 
-  @Test(enabled = false)
+  @Test
   public void testOldEnableDisable() throws InterruptedException {
     _gSetupTool.getClusterManagementTool().enableInstance(CLUSTER_NAME,
         _participants[0].getInstanceName(), false);
@@ -59,7 +65,7 @@ public class TestBatchEnableInstances extends TaskTestBase {
         _participants[0].getInstanceName(), true);
   }
 
-  @Test(enabled = false)
+  @Test
   public void testBatchEnableDisable() throws InterruptedException {
     _gSetupTool.getClusterManagementTool().enableInstance(CLUSTER_NAME,
         Arrays.asList(_participants[0].getInstanceName(), 
_participants[1].getInstanceName()),
@@ -78,7 +84,7 @@ public class TestBatchEnableInstances extends TaskTestBase {
         true);
   }
 
-  @Test(enabled = false)
+  @Test
   public void testOldDisableBatchEnable() throws InterruptedException {
     _gSetupTool.getClusterManagementTool().enableInstance(CLUSTER_NAME,
         _participants[0].getInstanceName(), false);
@@ -101,13 +107,40 @@ public class TestBatchEnableInstances extends 
TaskTestBase {
         _participants[0].getInstanceName(), true);
   }
 
-  @Test(enabled = false)
+  @Test
   public void testBatchDisableOldEnable() throws InterruptedException {
+    // disable 2 instances
     _gSetupTool.getClusterManagementTool().enableInstance(CLUSTER_NAME,
         Arrays.asList(_participants[0].getInstanceName(), 
_participants[1].getInstanceName()),
-        false);
-    _gSetupTool.getClusterManagementTool().enableInstance(CLUSTER_NAME,
-        _participants[0].getInstanceName(), true);
+        false, InstanceConstants.InstanceDisabledType.USER_OPERATION, 
"reason_1");
+    // check disabled type from InstanceValidationUtil
+    HelixDataAccessor dataAccessor =
+        new ZKHelixDataAccessor(CLUSTER_NAME, new 
ZkBaseDataAccessor<>(_gZkClient));
+    Assert.assertEquals(InstanceValidationUtil
+            .getInstanceHelixDisabledType(dataAccessor, 
_participants[1].getInstanceName()),
+        InstanceConstants.InstanceDisabledType.USER_OPERATION.toString());
+    // check disabled reason from getter in clusterConfig
+    ClusterConfig clusterConfig =
+        dataAccessor.getProperty(dataAccessor.keyBuilder().clusterConfig());
+    Assert.assertEquals(
+        
clusterConfig.getInstanceHelixDisabledReason(_participants[1].getInstanceName()),
+        "reason_1");
+    Assert.assertNotNull(
+        
clusterConfig.getInstanceHelixDisabledTimeStamp(_participants[0].getInstanceName()));
+    // enable the second instance
+    _gSetupTool.getClusterManagementTool()
+        .enableInstance(CLUSTER_NAME, _participants[0].getInstanceName(), 
true);
+    // check disabled type for second instance from InstanceValidationUtil and 
clusterConfig
+    Assert.assertEquals(InstanceValidationUtil
+            .getInstanceHelixDisabledType(dataAccessor, 
_participants[0].getInstanceName()),
+        InstanceConstants.INSTANCE_NOT_DISABLED);
+    clusterConfig = 
dataAccessor.getProperty(dataAccessor.keyBuilder().clusterConfig());
+    Assert.assertEquals(
+        
clusterConfig.getInstanceHelixDisabledType(_participants[0].getInstanceName()),
+        InstanceConstants.INSTANCE_NOT_DISABLED);
+    Assert.assertNull(
+        
clusterConfig.getInstanceHelixDisabledTimeStamp(_participants[0].getInstanceName()));
+
     Thread.sleep(2000);
 
     ExternalView externalView = _gSetupTool.getClusterManagementTool()
@@ -125,4 +158,5 @@ public class TestBatchEnableInstances extends TaskTestBase {
         Arrays.asList(_participants[0].getInstanceName(), 
_participants[1].getInstanceName()),
         true);
   }
+
 }
diff --git a/helix-core/src/test/java/org/apache/helix/mock/MockHelixAdmin.java 
b/helix-core/src/test/java/org/apache/helix/mock/MockHelixAdmin.java
index 6390edf73..87231d5d7 100644
--- a/helix-core/src/test/java/org/apache/helix/mock/MockHelixAdmin.java
+++ b/helix-core/src/test/java/org/apache/helix/mock/MockHelixAdmin.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 
 import org.apache.helix.BaseDataAccessor;
 import org.apache.helix.HelixAdmin;
@@ -49,6 +50,9 @@ import org.apache.helix.model.ResourceConfig;
 import org.apache.helix.model.StateModelDefinition;
 import org.apache.helix.zookeeper.datamodel.ZNRecord;
 
+import static 
org.apache.helix.manager.zk.ZKHelixAdmin.assembleInstanceBatchedDisabledInfo;
+
+
 public class MockHelixAdmin implements HelixAdmin {
 
   private HelixDataAccessor _dataAccessor;
@@ -285,6 +289,7 @@ public class MockHelixAdmin implements HelixAdmin {
     InstanceConfig instanceConfig = new InstanceConfig(record);
     instanceConfig.setInstanceEnabled(enabled);
     if (!enabled) {
+      instanceConfig.resetInstanceDisabledTypeAndReason();
       if (reason != null) {
         instanceConfig.setInstanceDisabledReason(reason);
       }
@@ -295,16 +300,46 @@ public class MockHelixAdmin implements HelixAdmin {
     _baseDataAccessor.set(instanceConfigPath, instanceConfig.getRecord(), 0);
   }
 
-  @Override public void enableInstance(String clusterName, List<String> 
instances,
-      boolean enabled) {
+  @Override
+  public void enableInstance(String clusterName, List<String> instances, 
boolean enabled) {
+    enableInstance(clusterName, instances, enabled, null, null);
+  }
+
+  @Override
+  public void enableInstance(String clusterName, List<String> instances, 
boolean enabled,
+      InstanceConstants.InstanceDisabledType disabledType, String reason) {
+
+    String path = PropertyPathBuilder.clusterConfig(clusterName);
 
+    if (!_baseDataAccessor.exists(path, 0)) {
+      _baseDataAccessor.create(path, new ZNRecord(clusterName), 0);
+    }
+
+    ZNRecord record = (ZNRecord) _baseDataAccessor.get(path, null, 0);
+    ClusterConfig clusterConfig = new ClusterConfig(record);
+
+    Map<String, String> disabledInstances = new TreeMap<>();
+    if (clusterConfig.getDisabledInstances() != null) {
+      disabledInstances.putAll(clusterConfig.getDisabledInstances());
+    }
+    if (enabled) {
+      disabledInstances.keySet().removeAll(instances);
+    } else {
+      for (String disabledInstance : instances) {
+        disabledInstances
+            .put(disabledInstance, 
assembleInstanceBatchedDisabledInfo(disabledType, reason));
+      }
+    }
+    clusterConfig.setDisabledInstances(disabledInstances);
   }
 
-  @Override public void enableResource(String clusterName, String 
resourceName, boolean enabled) {
+  @Override
+  public void enableResource(String clusterName, String resourceName, boolean 
enabled) {
 
   }
 
-  @Override public void enablePartition(boolean enabled, String clusterName, 
String instanceName,
+  @Override
+  public void enablePartition(boolean enabled, String clusterName, String 
instanceName,
       String resourceName, List<String> partitionNames) {
 
   }
diff --git 
a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/InstancesAccessor.java
 
b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/InstancesAccessor.java
index fefe5c3cf..724a8ec44 100644
--- 
a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/InstancesAccessor.java
+++ 
b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/InstancesAccessor.java
@@ -44,6 +44,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.helix.HelixAdmin;
 import org.apache.helix.HelixDataAccessor;
 import org.apache.helix.HelixException;
+import org.apache.helix.constants.InstanceConstants;
 import org.apache.helix.manager.zk.ZKHelixDataAccessor;
 import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.InstanceConfig;
@@ -154,6 +155,8 @@ public class InstancesAccessor extends 
AbstractHelixResource {
       @QueryParam("command") String command,
       @QueryParam("continueOnFailures") boolean continueOnFailures,
       @QueryParam("skipZKRead") boolean skipZKRead,
+      @QueryParam("instanceDisabledType") String disabledType,
+      @QueryParam("instanceDisabledReason") String disabledReason,
       String content) {
     Command cmd;
     try {
@@ -179,7 +182,15 @@ public class InstancesAccessor extends 
AbstractHelixResource {
         admin.enableInstance(clusterId, enableInstances, true);
         break;
       case disable:
-        admin.enableInstance(clusterId, enableInstances, false);
+        InstanceConstants.InstanceDisabledType disabledTypeEnum = null;
+        if (disabledType != null) {
+          try {
+            disabledTypeEnum = 
InstanceConstants.InstanceDisabledType.valueOf(disabledType);
+          } catch (IllegalArgumentException ex) {
+            return badRequest("Invalid instanceDisabledType!");
+          }
+        }
+        admin.enableInstance(clusterId, enableInstances, false, 
disabledTypeEnum, disabledReason);
         break;
       case stoppable:
         return batchGetStoppableInstances(clusterId, node, skipZKRead, 
continueOnFailures);
diff --git 
a/helix-rest/src/test/java/org/apache/helix/rest/server/TestInstancesAccessor.java
 
b/helix-rest/src/test/java/org/apache/helix/rest/server/TestInstancesAccessor.java
index 7f046e49f..7e9058ff8 100644
--- 
a/helix-rest/src/test/java/org/apache/helix/rest/server/TestInstancesAccessor.java
+++ 
b/helix-rest/src/test/java/org/apache/helix/rest/server/TestInstancesAccessor.java
@@ -137,37 +137,45 @@ public class TestInstancesAccessor extends 
AbstractTestClass {
     System.out.println("End test :" + TestHelper.getTestMethodName());
   }
 
-  @Test(enabled = false)
+  @Test
   public void testUpdateInstances() throws IOException {
     // TODO: Reenable the test after storage node fix the problem
     // Batch disable instances
 
-    List<String> instancesToDisable = Arrays.asList(new String[] {
-        CLUSTER_NAME + "localhost_12918", CLUSTER_NAME + "localhost_12919",
-        CLUSTER_NAME + "localhost_12920"
-    });
-    Entity entity = Entity.entity(
-        OBJECT_MAPPER.writeValueAsString(ImmutableMap
+    List<String> instancesToDisable = Arrays.asList(new String[]{
+        CLUSTER_NAME + "localhost_12918",
+        CLUSTER_NAME + "localhost_12919", CLUSTER_NAME + "localhost_12920"});
+    Entity entity = Entity.entity(OBJECT_MAPPER.writeValueAsString(ImmutableMap
             .of(InstancesAccessor.InstancesProperties.instances.name(), 
instancesToDisable)),
         MediaType.APPLICATION_JSON_TYPE);
-    post("clusters/" + CLUSTER_NAME + "/instances", ImmutableMap.of("command", 
"disable"), entity,
-        Response.Status.OK.getStatusCode());
+    post("clusters/" + CLUSTER_NAME + "/instances", ImmutableMap
+        .of("command", "disable", "instanceDisabledType", "USER_OPERATION",
+            "instanceDisabledReason", "reason_1"), entity, 
Response.Status.OK.getStatusCode());
     ClusterConfig clusterConfig = 
_configAccessor.getClusterConfig(CLUSTER_NAME);
     Assert.assertEquals(clusterConfig.getDisabledInstances().keySet(),
         new HashSet<>(instancesToDisable));
-
-    instancesToDisable = Arrays.asList(new String[] {
-        CLUSTER_NAME + "localhost_12918", CLUSTER_NAME + "localhost_12920"
-    });
-    entity = Entity.entity(
-        OBJECT_MAPPER.writeValueAsString(ImmutableMap
+    Assert
+        .assertEquals(clusterConfig.getInstanceHelixDisabledType(CLUSTER_NAME 
+ "localhost_12918"),
+            "USER_OPERATION");
+    Assert.assertEquals(
+        clusterConfig.getInstanceHelixDisabledReason(CLUSTER_NAME + 
"localhost_12918"), "reason_1");
+
+    instancesToDisable = Arrays
+        .asList(new String[]{CLUSTER_NAME + "localhost_12918", CLUSTER_NAME + 
"localhost_12920"});
+    entity = Entity.entity(OBJECT_MAPPER.writeValueAsString(ImmutableMap
             .of(InstancesAccessor.InstancesProperties.instances.name(), 
instancesToDisable)),
         MediaType.APPLICATION_JSON_TYPE);
-    post("clusters/" + CLUSTER_NAME + "/instances", ImmutableMap.of("command", 
"enable"), entity,
-        Response.Status.OK.getStatusCode());
+    post("clusters/" + CLUSTER_NAME + "/instances", ImmutableMap
+        .of("command", "enable", "instanceDisabledType", "USER_OPERATION", 
"instanceDisabledReason",
+            "reason_1"), entity, Response.Status.OK.getStatusCode());
     clusterConfig = _configAccessor.getClusterConfig(CLUSTER_NAME);
     Assert.assertEquals(clusterConfig.getDisabledInstances().keySet(),
         new HashSet<>(Arrays.asList(CLUSTER_NAME + "localhost_12919")));
+    Assert
+        .assertEquals(clusterConfig.getInstanceHelixDisabledType(CLUSTER_NAME 
+ "localhost_12918"),
+            "INSTANCE_NOT_DISABLED");
+    Assert
+        .assertNull(clusterConfig.getInstanceHelixDisabledReason(CLUSTER_NAME 
+ "localhost_12918"));
     System.out.println("End test :" + TestHelper.getTestMethodName());
   }
 

Reply via email to