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

xyuanlu 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 0f95ef313 Support preserving history of HELIX_ENABLED legacy fields 
when combo of instance operation and old helix version are used (#2987)
0f95ef313 is described below

commit 0f95ef3134d92ac46e6c75750f9284c0e1cece59
Author: Zachary Pinto <zapi...@linkedin.com>
AuthorDate: Fri Jan 17 20:52:02 2025 -0800

    Support preserving history of HELIX_ENABLED legacy fields when combo of 
instance operation and old helix version are used (#2987)
    
    When the new instance operation APIs are used, we need to consider that an 
older version of helix could have been used to modify the legacy fields of 
HELIX_ENABLED, HELIX_DISABLED_TYPE, and HELIX_DISABLED_REASON. When this 
happens, we will write those legacy fields to the USER source instance 
operation to preserve the history when another source sets the instance 
operation. When the other source re-enables the instance, we should restore the 
old legacy fields with what was previously set.
---
 .../apache/helix/constants/InstanceConstants.java  |   2 +
 .../event/helix/DefaultCloudEventCallbackImpl.java |  22 +-
 .../org/apache/helix/manager/zk/ZKHelixAdmin.java  |  19 +-
 .../org/apache/helix/model/InstanceConfig.java     | 233 +++++++++++++--------
 .../event/TestDefaultCloudEventCallbackImpl.java   |   2 +-
 .../java/org/apache/helix/mock/MockHelixAdmin.java |  10 +-
 .../org/apache/helix/model/TestInstanceConfig.java | 108 +++++++++-
 7 files changed, 284 insertions(+), 112 deletions(-)

diff --git 
a/helix-common/src/main/java/org/apache/helix/constants/InstanceConstants.java 
b/helix-common/src/main/java/org/apache/helix/constants/InstanceConstants.java
index df6c37bb6..bc87a4f68 100644
--- 
a/helix-common/src/main/java/org/apache/helix/constants/InstanceConstants.java
+++ 
b/helix-common/src/main/java/org/apache/helix/constants/InstanceConstants.java
@@ -54,7 +54,9 @@ public class InstanceConstants {
      *
      * @param disabledType InstanceDisabledType
      * @return InstanceOperationTrigger
+     * @deprecated The concept of InstanceDisabledType mapping directly to an 
InstanceOperationSource is no longer used.
      */
+    @Deprecated
     public static InstanceOperationSource 
instanceDisabledTypeToInstanceOperationSource(
         InstanceDisabledType disabledType) {
       switch (disabledType) {
diff --git 
a/helix-core/src/main/java/org/apache/helix/cloud/event/helix/DefaultCloudEventCallbackImpl.java
 
b/helix-core/src/main/java/org/apache/helix/cloud/event/helix/DefaultCloudEventCallbackImpl.java
index 20c500116..d41c1157a 100644
--- 
a/helix-core/src/main/java/org/apache/helix/cloud/event/helix/DefaultCloudEventCallbackImpl.java
+++ 
b/helix-core/src/main/java/org/apache/helix/cloud/event/helix/DefaultCloudEventCallbackImpl.java
@@ -51,14 +51,9 @@ public class DefaultCloudEventCallbackImpl {
     LOG.info("DefaultCloudEventCallbackImpl disable Instance {}", 
manager.getInstanceName());
     if (InstanceValidationUtil
         .isEnabled(manager.getHelixDataAccessor(), manager.getInstanceName())) 
{
-      InstanceUtil.setInstanceOperation(manager.getConfigAccessor(),
-          manager.getHelixDataAccessor().getBaseDataAccessor(), 
manager.getClusterName(),
-          manager.getInstanceName(),
-              new InstanceConfig.InstanceOperation.Builder().setOperation(
-                      InstanceConstants.InstanceOperation.DISABLE)
-                  
.setSource(InstanceConstants.InstanceOperationSource.AUTOMATION)
-                  .setReason(message)
-                  .build());
+      manager.getClusterManagmentTool()
+          .enableInstance(manager.getClusterName(), manager.getInstanceName(), 
false,
+              InstanceConstants.InstanceDisabledType.CLOUD_EVENT, message);
     }
     
HelixEventHandlingUtil.updateCloudEventOperationInClusterConfig(manager.getClusterName(),
         manager.getInstanceName(), 
manager.getHelixDataAccessor().getBaseDataAccessor(), false,
@@ -79,13 +74,10 @@ public class DefaultCloudEventCallbackImpl {
     HelixEventHandlingUtil
         .updateCloudEventOperationInClusterConfig(manager.getClusterName(), 
instanceName,
             manager.getHelixDataAccessor().getBaseDataAccessor(), true, 
message);
-    InstanceUtil.setInstanceOperation(manager.getConfigAccessor(),
-        manager.getHelixDataAccessor().getBaseDataAccessor(), 
manager.getClusterName(),
-        manager.getInstanceName(),
-            new InstanceConfig.InstanceOperation.Builder().setOperation(
-                    InstanceConstants.InstanceOperation.ENABLE)
-                
.setSource(InstanceConstants.InstanceOperationSource.AUTOMATION).setReason(message)
-                .build());
+    if (HelixEventHandlingUtil.isInstanceDisabledForCloudEvent(instanceName, 
accessor)) {
+      
manager.getClusterManagmentTool().enableInstance(manager.getClusterName(), 
instanceName, true,
+          InstanceConstants.InstanceDisabledType.CLOUD_EVENT, message);
+    }
   }
 
   /**
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 b091b70ca..d5998a166 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
@@ -747,7 +747,7 @@ public class ZKHelixAdmin implements HelixAdmin {
 
     InstanceConfig.InstanceOperation instanceOperationObj = new 
InstanceConfig.InstanceOperation.Builder()
         
.setOperation(InstanceConstants.InstanceOperation.UNKNOWN).setReason(reason)
-        .setSource(operationSource != null ? operationSource : 
InstanceConstants.InstanceOperationSource.USER).build();
+        .setSource(operationSource).build();
     InstanceConfig instanceConfig = getInstanceConfig(clusterName, 
instanceName);
     instanceConfig.setInstanceOperation(instanceOperationObj);
 
@@ -2363,12 +2363,17 @@ public class ZKHelixAdmin implements HelixAdmin {
         }
 
         InstanceConfig config = new InstanceConfig(currentData);
-        config.setInstanceOperation(new 
InstanceConfig.InstanceOperation.Builder().setOperation(
-            enabled ? InstanceConstants.InstanceOperation.ENABLE
-                : 
InstanceConstants.InstanceOperation.DISABLE).setReason(reason).setSource(
-            disabledType != null
-                ? 
InstanceConstants.InstanceOperationSource.instanceDisabledTypeToInstanceOperationSource(
-                disabledType) : null).build());
+        config.setInstanceEnabled(enabled);
+        if (!enabled) {
+          // new disabled type and reason will overwrite existing ones.
+          config.resetInstanceDisabledTypeAndReason();
+          if (reason != null) {
+            config.setInstanceDisabledReason(reason);
+          }
+          if (disabledType != null) {
+            config.setInstanceDisabledType(disabledType);
+          }
+        }
         return config.getRecord();
       }
     }, AccessOption.PERSISTENT);
diff --git 
a/helix-core/src/main/java/org/apache/helix/model/InstanceConfig.java 
b/helix-core/src/main/java/org/apache/helix/model/InstanceConfig.java
index 7b5faddc3..b53e0b1fb 100644
--- a/helix-core/src/main/java/org/apache/helix/model/InstanceConfig.java
+++ b/helix-core/src/main/java/org/apache/helix/model/InstanceConfig.java
@@ -22,12 +22,14 @@ package org.apache.helix.model;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 import javax.annotation.Nullable;
 
@@ -49,6 +51,8 @@ import org.slf4j.LoggerFactory;
  * Instance configurations
  */
 public class InstanceConfig extends HelixProperty {
+  private static final Logger logger = 
LoggerFactory.getLogger(InstanceConfig.class.getName());
+
   /**
    * Configurable characteristics of an instance
    */
@@ -67,14 +71,22 @@ public class InstanceConfig extends HelixProperty {
     DELAY_REBALANCE_ENABLED,
     MAX_CONCURRENT_TASK,
     INSTANCE_INFO_MAP,
-    INSTANCE_CAPACITY_MAP, TARGET_TASK_THREAD_POOL_SIZE, 
HELIX_INSTANCE_OPERATIONS
+    INSTANCE_CAPACITY_MAP,
+    TARGET_TASK_THREAD_POOL_SIZE,
+    HELIX_INSTANCE_OPERATIONS
   }
 
   public static class InstanceOperation {
+    private static final String DEFAULT_INSTANCE_OPERATION_SOURCE =
+        InstanceConstants.InstanceOperationSource.USER.name();
     private final Map<String, String> _properties;
 
     private enum InstanceOperationProperties {
-      OPERATION, REASON, SOURCE, TIMESTAMP
+      OPERATION,
+      REASON,
+      SOURCE,
+      TIMESTAMP,
+      LEGACY_DISABLED_TYPE
     }
 
     private InstanceOperation(@Nullable Map<String, String> properties) {
@@ -91,6 +103,7 @@ public class InstanceConfig extends HelixProperty {
 
       /**
        * Set the operation type for this instance operation.
+       *
        * @param operationType InstanceOperation type of this instance 
operation.
        */
       public Builder setOperation(@Nullable 
InstanceConstants.InstanceOperation operationType) {
@@ -102,22 +115,44 @@ public class InstanceConfig extends HelixProperty {
 
       /**
        * Set the reason for this instance operation.
-       * @param reason
+       *
+       * @param reason the reason for this instance operation.
        */
       public Builder setReason(String reason) {
-        _properties.put(InstanceOperationProperties.REASON.name(), reason != 
null ? reason : "");
+        if (reason == null) {
+          logger.error("Reason cannot be set to null. Skipped setting the 
field.");
+          return this;
+        }
+        _properties.put(InstanceOperationProperties.REASON.name(), reason);
         return this;
       }
 
       /**
        * Set the source for this instance operation.
-       * @param source InstanceOperationSource
-       *              that caused this instance operation to be triggered.
+       *
+       * @param source InstanceOperationSource that caused this instance 
operation to be triggered.
+       * @return Builder
        */
       public Builder setSource(InstanceConstants.InstanceOperationSource 
source) {
         _properties.put(InstanceOperationProperties.SOURCE.name(),
-            source == null ? 
InstanceConstants.InstanceOperationSource.USER.name()
-                : source.name());
+            source == null ? DEFAULT_INSTANCE_OPERATION_SOURCE : 
source.name());
+        return this;
+      }
+
+      /**
+       * Set the HELIX_DISABLED_TYPE which is a legacy field that must be 
stored, so we can write it
+       * back when we write back to the legacy fields.
+       *
+       * @param disabledType InstanceDisabledType
+       * @return Builder
+       */
+      private Builder 
setLegacyDisabledType(InstanceConstants.InstanceDisabledType disabledType) {
+        if (disabledType == null) {
+          logger.error("LEGACY_DISABLED_TYPE cannot be set to null. Skipped 
setting the field.");
+          return this;
+        }
+        
_properties.put(InstanceOperationProperties.LEGACY_DISABLED_TYPE.name(),
+            disabledType.name());
         return this;
       }
 
@@ -126,6 +161,8 @@ public class InstanceConfig extends HelixProperty {
           throw new IllegalArgumentException(
               "Instance operation type is not set, this is a required field.");
         }
+        _properties.putIfAbsent(InstanceOperationProperties.SOURCE.name(),
+            DEFAULT_INSTANCE_OPERATION_SOURCE);
         _properties.put(InstanceOperationProperties.TIMESTAMP.name(),
             String.valueOf(System.currentTimeMillis()));
         return new InstanceOperation(_properties);
@@ -134,6 +171,7 @@ public class InstanceConfig extends HelixProperty {
 
     /**
      * Get the operation type of this instance operation.
+     *
      * @return the InstanceOperation type
      */
     public InstanceConstants.InstanceOperation getOperation() throws 
IllegalArgumentException {
@@ -142,8 +180,8 @@ public class InstanceConfig extends HelixProperty {
     }
 
     /**
-     * Get the reason for this instance operation.
-     * If the reason is not set, it will default to an empty string.
+     * Get the reason for this instance operation. If the reason is not set, 
it will default to an
+     * empty string.
      *
      * @return the reason for this instance operation.
      */
@@ -152,12 +190,10 @@ public class InstanceConfig extends HelixProperty {
     }
 
     /**
-     * Get the InstanceOperationSource
-     * that caused this instance operation to be triggered.
-     * If the source is not set, it will default to DEFAULT.
+     * Get the InstanceOperationSource that caused this instance operation to 
be triggered. If the
+     * source is not set, it will default to DEFAULT.
      *
-     * @return the InstanceOperationSource
-     *that caused this instance operation to be triggered.
+     * @return the InstanceOperationSource that caused this instance operation 
to be triggered.
      */
     public InstanceConstants.InstanceOperationSource getSource() {
       return InstanceConstants.InstanceOperationSource.valueOf(
@@ -165,6 +201,12 @@ public class InstanceConfig extends HelixProperty {
               InstanceConstants.InstanceOperationSource.USER.name()));
     }
 
+    private InstanceConstants.InstanceDisabledType getLegacyDisabledType() {
+      return InstanceConstants.InstanceDisabledType.valueOf(
+          
_properties.getOrDefault(InstanceOperationProperties.LEGACY_DISABLED_TYPE.name(),
+              
InstanceConstants.InstanceDisabledType.DEFAULT_INSTANCE_DISABLE_TYPE.name()));
+    }
+
     /**
      * Get the timestamp (milliseconds from epoch) when this instance 
operation was triggered.
      *
@@ -442,7 +484,8 @@ public class InstanceConfig extends HelixProperty {
    */
   @Deprecated
   public void setInstanceDisabledType(InstanceConstants.InstanceDisabledType 
disabledType) {
-    if 
(getInstanceOperation().getOperation().equals(InstanceConstants.InstanceOperation.DISABLE))
 {
+    if 
(getInstanceOperation().getOperation().equals(InstanceConstants.InstanceOperation.DISABLE)
+        && disabledType != 
InstanceConstants.InstanceDisabledType.DEFAULT_INSTANCE_DISABLE_TYPE) {
       _record.setSimpleField(InstanceConfigProperty.HELIX_DISABLED_TYPE.name(),
           disabledType.name());
     }
@@ -501,50 +544,42 @@ public class InstanceConfig extends HelixProperty {
     return _deserializedInstanceOperations;
   }
 
-  /**
-   * Set the instance operation for this instance.
-   * This method also sets the HELIX_ENABLED, HELIX_DISABLED_REASON, and 
HELIX_DISABLED_TYPE fields
-   * for backwards compatibility.
-   *
-   * @param operation the instance operation
-   */
-  public void setInstanceOperation(InstanceOperation operation) {
-    List<InstanceOperation> deserializedInstanceOperations = 
getInstanceOperations();
-
+  private void updateInstanceOperation(List<InstanceOperation> operations,
+      InstanceOperation operation) {
     if (operation.getSource() == 
InstanceConstants.InstanceOperationSource.ADMIN) {
-      deserializedInstanceOperations.clear();
+      operations.clear();
     } else {
-      // Remove the instance operation with the same source if it exists.
-      deserializedInstanceOperations.removeIf(
-          instanceOperation -> instanceOperation.getSource() == 
operation.getSource());
+      // Remove existing operation with the same source.
+      operations.removeIf(op -> op.getSource() == operation.getSource());
     }
+
     if (operation.getOperation() == 
InstanceConstants.InstanceOperation.ENABLE) {
-      // Insert the operation after the last ENABLE or at the beginning if 
there isn't ENABLE in the list.
-      int insertIndex = 0;
-      for (int i = deserializedInstanceOperations.size() - 1; i >= 0; i--) {
-        if (deserializedInstanceOperations.get(i).getOperation()
-            == InstanceConstants.InstanceOperation.ENABLE) {
-          insertIndex = i + 1;
-          break;
-        }
-      }
-      deserializedInstanceOperations.add(insertIndex, operation);
+      // Insert ENABLE operation after the last existing ENABLE, or at the 
beginning.
+      int insertIndex = (int) IntStream.range(0, operations.size()).filter(
+              i -> operations.get(i).getOperation() == 
InstanceConstants.InstanceOperation.ENABLE)
+          .reduce((first, second) -> second).orElse(-1) + 1;
+
+      operations.add(insertIndex, operation);
     } else {
-      deserializedInstanceOperations.add(operation);
+      operations.add(operation);
     }
-    // Set the actual field in the ZnRecord
-    
_record.setListField(InstanceConfigProperty.HELIX_INSTANCE_OPERATIONS.name(),
-        deserializedInstanceOperations.stream().map(instanceOperation -> {
-          try {
-            return 
_objectMapper.writeValueAsString(instanceOperation.getProperties());
-          } catch (JsonProcessingException e) {
-            throw new HelixException(
-                "Failed to serialize instance operation for instance: " + 
_record.getId()
-                    + " Can't set the instance operation to: " + 
operation.getOperation(), e);
-          }
-        }).collect(Collectors.toList()));
+  }
 
-    // TODO: Remove this when we are sure that all users are using the new 
InstanceOperation only and HELIX_ENABLED is removed.
+  private List<String> serializeInstanceOperations(List<InstanceOperation> 
operations) {
+    return operations.stream().map(op -> {
+      try {
+        return _objectMapper.writeValueAsString(op.getProperties());
+      } catch (JsonProcessingException e) {
+        throw new HelixException(
+            "Failed to serialize instance operation for instance: " + 
_record.getId()
+                + ". Can't set the instance operation to: " + 
op.getOperation(), e);
+      }
+    }).collect(Collectors.toList());
+  }
+
+  private void setLegacyFieldsForInstanceOperation(InstanceOperation 
operation) {
+    // TODO: Remove this when we are sure that all users are using the new 
InstanceOperation only
+    //  and HELIX_ENABLED is removed.
     if (operation.getOperation() == 
InstanceConstants.InstanceOperation.DISABLE) {
       // We are still setting the HELIX_ENABLED field for backwards 
compatibility.
       // It is possible that users will be using earlier version of HelixAdmin 
or helix-rest
@@ -555,31 +590,57 @@ public class InstanceConfig extends HelixProperty {
         setInstanceEnabledHelper(false, operation.getTimestamp());
       }
 
-      
_record.setSimpleField(InstanceConfigProperty.HELIX_DISABLED_REASON.name(),
-          operation.getReason());
-      _record.setSimpleField(InstanceConfigProperty.HELIX_DISABLED_TYPE.name(),
-          
InstanceConstants.InstanceDisabledType.DEFAULT_INSTANCE_DISABLE_TYPE.name());
+      resetInstanceDisabledTypeAndReason();
+      setInstanceDisabledReason(operation.getReason());
+      setInstanceDisabledType(operation.getLegacyDisabledType());
     } else if (operation.getOperation() == 
InstanceConstants.InstanceOperation.ENABLE) {
-      // If any of the other InstanceOperations are of type DISABLE, set that 
in the HELIX_ENABLED,
-      // HELIX_DISABLED_REASON, and HELIX_DISABLED_TYPE fields.
-      InstanceOperation latestDisableInstanceOperation = null;
-      for (InstanceOperation instanceOperation : getInstanceOperations()) {
-        if (instanceOperation.getOperation() == 
InstanceConstants.InstanceOperation.DISABLE && (
-            latestDisableInstanceOperation == null || 
instanceOperation.getTimestamp()
-                > latestDisableInstanceOperation.getTimestamp())) {
-          latestDisableInstanceOperation = instanceOperation;
+      // Ensure HELIX_ENABLED reflects the latest disable operation if 
applicable.
+      InstanceOperation latestDisableInstanceOperation = 
getInstanceOperations().stream()
+          .filter(op -> op.getOperation() == 
InstanceConstants.InstanceOperation.DISABLE)
+          
.max(Comparator.comparingLong(InstanceOperation::getTimestamp)).orElse(null);
+
+      if (!_record.getBooleanField(InstanceConfigProperty.HELIX_ENABLED.name(),
+          HELIX_ENABLED_DEFAULT_VALUE)) {
+        // Only set the disable reason and disable type if the HELIX_ENABLED 
is false because HELIX_ENABLED
+        // being true takes precedence over an existing latest disable 
operation existing.
+        if (latestDisableInstanceOperation != null) {
+          
setInstanceDisabledReason(latestDisableInstanceOperation.getReason());
+          
setInstanceDisabledType(latestDisableInstanceOperation.getLegacyDisabledType());
+        } else {
+          setInstanceEnabledHelper(true, operation.getTimestamp());
         }
       }
+    }
+  }
 
-      if (latestDisableInstanceOperation != null) {
-        
_record.setSimpleField(InstanceConfigProperty.HELIX_DISABLED_REASON.name(),
-            latestDisableInstanceOperation.getReason());
-        
_record.setSimpleField(InstanceConfigProperty.HELIX_DISABLED_TYPE.name(),
-            
InstanceConstants.InstanceDisabledType.DEFAULT_INSTANCE_DISABLE_TYPE.name());
-      } else {
-        setInstanceEnabledHelper(true, operation.getTimestamp());
-      }
+  /**
+   * Set the instance operation for this instance. This method also sets the 
HELIX_ENABLED,
+   * HELIX_DISABLED_REASON, and HELIX_DISABLED_TYPE fields for backwards 
compatibility.
+   *
+   * @param operation the instance operation
+   */
+  public void setInstanceOperation(InstanceOperation operation) {
+    List<InstanceOperation> operations = getInstanceOperations();
+
+    // TODO: This can be removed when HELIX_ENABLED is removed.
+    // This is used for backwards compatibility. getInstanceOperation will 
respect the old
+    // legacy field of HELIX_ENABLED which could have been set by older 
versions of Helix.
+    // If the HELIX_ENABLED is set by an older version of Helix, we need to 
sync that value to the
+    // new InstanceOperation field when any new instance operation is set.
+    // We only need to do this if there is already an instance operation set. 
If there isn't, then
+    // the default is ENABLE with a source of DEFAULT.
+    if (!operations.isEmpty()) {
+      updateInstanceOperation(operations, getInstanceOperation());
     }
+
+    // Set the instance operation passed in.
+    updateInstanceOperation(operations, operation);
+
+    // Serialize and update ZnRecord.
+    
_record.setListField(InstanceConfigProperty.HELIX_INSTANCE_OPERATIONS.name(),
+        serializeInstanceOperations(operations));
+
+    setLegacyFieldsForInstanceOperation(operation);
   }
 
   /**
@@ -648,12 +709,19 @@ public class InstanceConfig extends HelixProperty {
         HELIX_ENABLED_DEFAULT_VALUE)
         && 
(InstanceConstants.INSTANCE_DISABLED_OVERRIDABLE_OPERATIONS.contains(
         activeInstanceOperation.getOperation()))) {
-      return new InstanceOperation.Builder().setOperation(
-              
InstanceConstants.InstanceOperation.DISABLE).setReason(getInstanceDisabledReason())
-          .setSource(
-              
InstanceConstants.InstanceOperationSource.instanceDisabledTypeToInstanceOperationSource(
-                  
InstanceConstants.InstanceDisabledType.valueOf(getInstanceDisabledType())))
-          .build();
+      InstanceOperation.Builder instanceOperationBuilder =
+          new 
InstanceOperation.Builder().setOperation(InstanceConstants.InstanceOperation.DISABLE)
+              .setReason(getInstanceDisabledReason());
+
+      try {
+        instanceOperationBuilder.setLegacyDisabledType(
+            
InstanceConstants.InstanceDisabledType.valueOf(getInstanceDisabledType()));
+      } catch (IllegalArgumentException e) {
+        _logger.error("Invalid instance disabled type for instance: " + 
_record.getId()
+            + ". Defaulting to DEFAULT_INSTANCE_DISABLE_TYPE.");
+      }
+
+      return instanceOperationBuilder.build();
     }
 
     // if instance operation is DISABLE, we override it to ENABLE if 
HELIX_ENABLED set to true
@@ -662,8 +730,10 @@ public class InstanceConfig extends HelixProperty {
       // we always set HELIX_ENABLED to false
       // If instance is enabled by old version helix (not having instance 
operation), the instance config
       // will have HELIX_ENABLED set to true. In this case, we should override 
the instance operation to ENABLE
-      if 
("true".equals(_record.getSimpleField(InstanceConfigProperty.HELIX_ENABLED.name())))
 {
-        return new 
InstanceOperation.Builder().setOperation(InstanceConstants.InstanceOperation.ENABLE).build();
+      if (_record.getBooleanField(InstanceConfigProperty.HELIX_ENABLED.name(),
+          HELIX_ENABLED_DEFAULT_VALUE)) {
+        return new InstanceOperation.Builder().setOperation(
+            InstanceConstants.InstanceOperation.ENABLE).build();
       }
     }
 
@@ -1098,7 +1168,6 @@ public class InstanceConfig extends HelixProperty {
     if (host != null && port > 0) {
       config.setHostName(host);
       config.setPort(String.valueOf(port));
-
     }
 
     if (config.getHostName() == null) {
diff --git 
a/helix-core/src/test/java/org/apache/helix/cloud/event/TestDefaultCloudEventCallbackImpl.java
 
b/helix-core/src/test/java/org/apache/helix/cloud/event/TestDefaultCloudEventCallbackImpl.java
index 5ea42dc3e..2f03cb3dd 100644
--- 
a/helix-core/src/test/java/org/apache/helix/cloud/event/TestDefaultCloudEventCallbackImpl.java
+++ 
b/helix-core/src/test/java/org/apache/helix/cloud/event/TestDefaultCloudEventCallbackImpl.java
@@ -53,7 +53,7 @@ public class TestDefaultCloudEventCallbackImpl extends 
ZkStandAloneCMTestBase {
         .isEnabled(_manager.getHelixDataAccessor(), 
_instanceManager.getInstanceName()));
     Assert.assertEquals(_manager.getConfigAccessor()
         .getInstanceConfig(CLUSTER_NAME, 
_instanceManager.getInstanceName()).getInstanceOperation()
-        .getSource(), InstanceConstants.InstanceOperationSource.AUTOMATION);
+        .getSource(), InstanceConstants.InstanceOperationSource.USER);
 
     _admin.enableInstance(CLUSTER_NAME, _instanceManager.getInstanceName(), 
false);
     _impl.disableInstance(_instanceManager, null);
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 c5c5626ff..5a1a8a5bc 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
@@ -287,12 +287,16 @@ public class MockHelixAdmin implements HelixAdmin {
 
     ZNRecord record = (ZNRecord) _baseDataAccessor.get(instanceConfigPath, 
null, 0);
     InstanceConfig instanceConfig = new InstanceConfig(record);
-    instanceConfig.setInstanceOperation(new 
InstanceConfig.InstanceOperation.Builder().setOperation(
-            enabled ? InstanceConstants.InstanceOperation.ENABLE
-                : 
InstanceConstants.InstanceOperation.DISABLE).setReason(reason).build());
+    instanceConfig.setInstanceEnabled(enabled);
     if (!enabled) {
       // TODO: Replace this when the HELIX_ENABLED and HELIX_DISABLED fields 
are removed.
       instanceConfig.resetInstanceDisabledTypeAndReason();
+      if (reason != null) {
+        instanceConfig.setInstanceDisabledReason(reason);
+      }
+      if (disabledType != null) {
+        instanceConfig.setInstanceDisabledType(disabledType);
+      }
     }
     _baseDataAccessor.set(instanceConfigPath, instanceConfig.getRecord(), 0);
   }
diff --git 
a/helix-core/src/test/java/org/apache/helix/model/TestInstanceConfig.java 
b/helix-core/src/test/java/org/apache/helix/model/TestInstanceConfig.java
index f0081bec0..506710921 100644
--- a/helix-core/src/test/java/org/apache/helix/model/TestInstanceConfig.java
+++ b/helix-core/src/test/java/org/apache/helix/model/TestInstanceConfig.java
@@ -56,7 +56,7 @@ public class TestInstanceConfig {
     
instanceConfig.setInstanceDisabledType(InstanceConstants.InstanceDisabledType.USER_OPERATION);
 
     Assert.assertEquals(instanceConfig.getRecord().getSimpleFields()
-        .get(InstanceConfig.InstanceConfigProperty.HELIX_ENABLED.toString()), 
"true");
+        .get(InstanceConfig.InstanceConfigProperty.HELIX_ENABLED.toString()), 
null);
     Assert.assertEquals(instanceConfig.getRecord().getSimpleFields()
         
.get(InstanceConfig.InstanceConfigProperty.HELIX_DISABLED_REASON.toString()), 
null);
     Assert.assertEquals(instanceConfig.getRecord().getSimpleFields()
@@ -81,14 +81,114 @@ public class TestInstanceConfig {
     InstanceConfig instanceConfig =
         new 
InstanceConfig.Builder().setInstanceOperation(InstanceConstants.InstanceOperation.DISABLE).build("id");
     Assert.assertFalse(instanceConfig.getInstanceEnabled());
-    // assume an old version client enabled the instance by setting 
HELIX_ENABLED to true
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getOperation(),
+        InstanceConstants.InstanceOperation.DISABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getSource(),
+        InstanceConstants.InstanceOperationSource.USER);
+
+    // Assume an old version client enabled the instance by setting 
HELIX_ENABLED to true
     
instanceConfig.getRecord().setSimpleField(InstanceConfig.InstanceConfigProperty.HELIX_ENABLED.name(),
 "true");
     Assert.assertTrue(instanceConfig.getInstanceEnabled());
-    
instanceConfig.setInstanceOperation(InstanceConstants.InstanceOperation.ENABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getOperation(),
+        InstanceConstants.InstanceOperation.ENABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getSource(),
+        InstanceConstants.InstanceOperationSource.USER);
 
-    // disable the instance by setting HELIX_ENABLED to false
+    // Automation source then disables the instance and writes the 
HELIX_ENABLED field to USER source's
+    // instance operation
+    instanceConfig.setInstanceOperation(new 
InstanceConfig.InstanceOperation.Builder().setSource(
+            InstanceConstants.InstanceOperationSource.AUTOMATION)
+        
.setOperation(InstanceConstants.InstanceOperation.DISABLE).setReason("disableReason")
+        .build());
+    Assert.assertFalse(instanceConfig.getInstanceEnabled());
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getOperation(),
+        InstanceConstants.InstanceOperation.DISABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getSource(),
+        InstanceConstants.InstanceOperationSource.AUTOMATION);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getReason(), 
"disableReason");
+    Assert.assertEquals(instanceConfig.getInstanceDisabledType(),
+        
InstanceConstants.InstanceDisabledType.DEFAULT_INSTANCE_DISABLE_TYPE.toString());
+    Assert.assertEquals(instanceConfig.getInstanceDisabledReason(), 
"disableReason");
+
+    // Automation source then enables the instance
+    instanceConfig.setInstanceOperation(new 
InstanceConfig.InstanceOperation.Builder().setSource(
+            InstanceConstants.InstanceOperationSource.AUTOMATION)
+        .setOperation(InstanceConstants.InstanceOperation.ENABLE).build());
+    // This should be enabled because we write the currently set legacy 
HELIX_ENABLED field into the instance operation
+    // associated with its source before the AUTOMATION source's operation is 
set.
+    Assert.assertTrue(instanceConfig.getInstanceEnabled());
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getOperation(),
+        InstanceConstants.InstanceOperation.ENABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getSource(),
+        InstanceConstants.InstanceOperationSource.AUTOMATION);
+
+    // Disable the instance with legacy HELIX_ENABLED field set to false
     
instanceConfig.getRecord().setSimpleField(InstanceConfig.InstanceConfigProperty.HELIX_ENABLED.name(),
 "false");
     Assert.assertFalse(instanceConfig.getInstanceEnabled());
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getOperation(),
+        InstanceConstants.InstanceOperation.DISABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getSource(),
+        InstanceConstants.InstanceOperationSource.USER);
+
+    // Enable the instance with automation source which should not override 
the HELIX_ENABLED field
+    // because the source that set HELIX_ENABLED is USER
+    instanceConfig.setInstanceOperation(new 
InstanceConfig.InstanceOperation.Builder().setSource(
+            InstanceConstants.InstanceOperationSource.AUTOMATION)
+        .setOperation(InstanceConstants.InstanceOperation.ENABLE).build());
+    Assert.assertFalse(instanceConfig.getInstanceEnabled());
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getOperation(),
+        InstanceConstants.InstanceOperation.DISABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getSource(),
+        InstanceConstants.InstanceOperationSource.USER);
+
+    // Enable the instance with user source which should override the 
HELIX_ENABLED
+    instanceConfig.setInstanceOperation(new 
InstanceConfig.InstanceOperation.Builder().setSource(
+            InstanceConstants.InstanceOperationSource.USER)
+        .setOperation(InstanceConstants.InstanceOperation.ENABLE).build());
+    Assert.assertTrue(instanceConfig.getInstanceEnabled());
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getOperation(),
+        InstanceConstants.InstanceOperation.ENABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getSource(),
+        InstanceConstants.InstanceOperationSource.USER);
+
+    // Disable the instance with legacy HELIX_ENABLED field set to false
+    instanceConfig.getRecord()
+        
.setSimpleField(InstanceConfig.InstanceConfigProperty.HELIX_ENABLED.name(), 
"false");
+    
instanceConfig.setInstanceDisabledType(InstanceConstants.InstanceDisabledType.USER_OPERATION);
+    instanceConfig.setInstanceDisabledReason("foo");
+    Assert.assertFalse(instanceConfig.getInstanceEnabled());
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getOperation(),
+        InstanceConstants.InstanceOperation.DISABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getSource(),
+        InstanceConstants.InstanceOperationSource.USER);
+
+    // Disable with automation source
+    instanceConfig.setInstanceOperation(new 
InstanceConfig.InstanceOperation.Builder().setSource(
+            InstanceConstants.InstanceOperationSource.AUTOMATION)
+        
.setOperation(InstanceConstants.InstanceOperation.DISABLE).setReason("bar").build());
+    Assert.assertFalse(instanceConfig.getInstanceEnabled());
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getOperation(),
+        InstanceConstants.InstanceOperation.DISABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getSource(),
+        InstanceConstants.InstanceOperationSource.AUTOMATION);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getReason(), 
"bar");
+    Assert.assertEquals(instanceConfig.getInstanceDisabledType(),
+        
InstanceConstants.InstanceDisabledType.DEFAULT_INSTANCE_DISABLE_TYPE.name());
+    Assert.assertEquals(instanceConfig.getInstanceDisabledReason(), "bar");
+
+    // Enable with automation source and return the instance to DISABLE with 
user source
+    instanceConfig.setInstanceOperation(new 
InstanceConfig.InstanceOperation.Builder().setSource(
+            InstanceConstants.InstanceOperationSource.AUTOMATION)
+        .setOperation(InstanceConstants.InstanceOperation.ENABLE).build());
+    Assert.assertFalse(instanceConfig.getInstanceEnabled());
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getOperation(),
+        InstanceConstants.InstanceOperation.DISABLE);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getSource(),
+        InstanceConstants.InstanceOperationSource.USER);
+    Assert.assertEquals(instanceConfig.getInstanceOperation().getReason(), 
"foo");
+    Assert.assertEquals(instanceConfig.getInstanceDisabledType(),
+        InstanceConstants.InstanceDisabledType.USER_OPERATION.name());
+    Assert.assertEquals(instanceConfig.getInstanceDisabledReason(), "foo");
   }
 
   @Test


Reply via email to