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