blob: 3908a9535cb5046a372ea696058402ae4bd46007 [file] [log] [blame]
package org.apache.helix.model;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.helix.HelixConstants;
import org.apache.helix.HelixProperty;
import org.apache.helix.ZNRecord;
import org.apache.helix.controller.rebalancer.Rebalancer;
import org.apache.helix.model.ResourceConfig.ResourceConfigProperty;
import org.apache.helix.task.FixedTargetTaskRebalancer;
import org.apache.helix.task.GenericTaskRebalancer;
import org.apache.helix.task.JobRebalancer;
import org.apache.helix.task.TaskRebalancer;
import org.apache.helix.task.WorkflowRebalancer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The ideal states of all partitions in a resource
*/
public class IdealState extends HelixProperty {
/**
* Properties that are persisted and are queryable for an ideal state
* Deprecated, use ResourceConfig.ResourceConfigProperty instead.
*/
@Deprecated
public enum IdealStateProperty {
NUM_PARTITIONS,
STATE_MODEL_DEF_REF,
STATE_MODEL_FACTORY_NAME,
REPLICAS,
MIN_ACTIVE_REPLICAS,
REBALANCE_DELAY,
@Deprecated
DELAY_REBALANCE_DISABLED,
@Deprecated
IDEAL_STATE_MODE,
REBALANCE_MODE,
REBALANCER_CLASS_NAME,
REBALANCE_TIMER_PERIOD,
REBALANCE_STRATEGY,
MAX_PARTITIONS_PER_INSTANCE,
INSTANCE_GROUP_TAG,
HELIX_ENABLED,
RESOURCE_GROUP_NAME,
RESOURCE_TYPE,
GROUP_ROUTING_ENABLED,
EXTERNAL_VIEW_DISABLED
}
public static final String QUERY_LIST = "PREFERENCE_LIST_QUERYS";
/**
* Deprecated, use ResourceConfig.ResourceConfigConstants instead
*/
@Deprecated
public enum IdealStateConstants {
ANY_LIVEINSTANCE
}
/**
* Deprecated.
* @see {@link RebalanceMode}
*/
@Deprecated
public enum IdealStateModeProperty {
AUTO,
CUSTOMIZED,
AUTO_REBALANCE
}
/**
* The mode used for rebalance. FULL_AUTO does both node location calculation and state
* assignment, SEMI_AUTO only does the latter, and CUSTOMIZED does neither. USER_DEFINED
* uses a Rebalancer implementation plugged in by the user. TASK designates that a
* {@link TaskRebalancer} instance should be used to rebalance this resource.
*/
public enum RebalanceMode {
FULL_AUTO,
SEMI_AUTO,
CUSTOMIZED,
USER_DEFINED,
TASK,
NONE
}
private static final Logger logger = LoggerFactory.getLogger(IdealState.class.getName());
/**
* Instantiate an ideal state for a resource
* @param resourceName the name of the resource
*/
public IdealState(String resourceName) {
super(resourceName);
}
/**
* Instantiate an ideal state from a record
* @param record ZNRecord corresponding to an ideal state
*/
public IdealState(ZNRecord record) {
super(record);
}
/**
* Get the associated resource
* @return the name of the resource
*/
public String getResourceName() {
return _record.getId();
}
/**
* Set the rebalance mode of the ideal state
* @param mode {@link IdealStateModeProperty}
*/
@Deprecated
public void setIdealStateMode(String mode) {
_record.setSimpleField(IdealStateProperty.IDEAL_STATE_MODE.toString(), mode);
RebalanceMode rebalanceMode = normalizeRebalanceMode(IdealStateModeProperty.valueOf(mode));
_record.setEnumField(IdealStateProperty.REBALANCE_MODE.toString(), rebalanceMode);
}
/**
* Set the rebalance mode of the resource
* @param rebalancerType
*/
public void setRebalanceMode(RebalanceMode rebalancerType) {
_record.setEnumField(IdealStateProperty.REBALANCE_MODE.toString(), rebalancerType);
IdealStateModeProperty idealStateMode = denormalizeRebalanceMode(rebalancerType);
_record.setEnumField(IdealStateProperty.IDEAL_STATE_MODE.toString(), idealStateMode);
}
/**
* Get the maximum number of partitions an instance can serve
* @return the partition capacity of an instance for this resource, or Integer.MAX_VALUE
*/
public int getMaxPartitionsPerInstance() {
return _record.getIntField(IdealStateProperty.MAX_PARTITIONS_PER_INSTANCE.toString(),
Integer.MAX_VALUE);
}
/**
* Define a custom rebalancer that implements {@link Rebalancer}
* @param rebalancerClassName the name of the custom rebalancing class
*/
public void setRebalancerClassName(String rebalancerClassName) {
_record
.setSimpleField(IdealStateProperty.REBALANCER_CLASS_NAME.toString(), rebalancerClassName);
}
/**
* Get the name of the user-defined rebalancer associated with this resource
* @return the rebalancer class name, or null if none is being used
*/
public String getRebalancerClassName() {
return _record.getSimpleField(IdealStateProperty.REBALANCER_CLASS_NAME.toString());
}
/**
* Specify the strategy for Helix to use to compute the partition-instance assignment,
* i,e, the custom rebalance strategy that implements {@link org.apache.helix.controller.rebalancer.strategy.RebalanceStrategy}
*
* @param rebalanceStrategy
* @return
*/
public void setRebalanceStrategy(String rebalanceStrategy) {
_record.setSimpleField(IdealStateProperty.REBALANCE_STRATEGY.name(), rebalanceStrategy);
}
/**
* Get the rebalance strategy for this resource.
*
* @return rebalance strategy, or null if not specified.
*/
public String getRebalanceStrategy() {
return _record.getSimpleField(IdealStateProperty.REBALANCE_STRATEGY.name());
}
/**
* Set the resource group name
* @param resourceGroupName
*/
public void setResourceGroupName(String resourceGroupName) {
_record.setSimpleField(IdealStateProperty.RESOURCE_GROUP_NAME.toString(), resourceGroupName);
}
/**
* Set the resource type
* @param resourceType
*/
public void setResourceType(String resourceType) {
_record.setSimpleField(IdealStateProperty.RESOURCE_TYPE.toString(), resourceType);
}
/**
* Get the resource type
* @return the resource type, or null if none is being set
*/
public String getResourceType() {
return _record.getSimpleField(IdealStateProperty.RESOURCE_TYPE.toString());
}
/**
* Set the delay time (in ms) that Helix should move the partition after an instance goes offline.
* @param delayInMilliseconds
*/
public void setRebalanceDelay(long delayInMilliseconds) {
_record.setLongField(IdealStateProperty.REBALANCE_DELAY.name(), delayInMilliseconds);
}
/**
* Get rebalance delay time (in ms).
* @return
*/
public long getRebalanceDelay() {
return _record.getLongField(IdealStateProperty.REBALANCE_DELAY.name(), -1);
}
/**
* Enable/Disable the delayed rebalance.
* By default it is enabled if not set.
*
* @param enabled
*/
public void setDelayRebalanceEnabled(boolean enabled) {
_record.setBooleanField(ResourceConfigProperty.DELAY_REBALANCE_ENABLED.name(), enabled);
}
/**
* Whether the delay rebalance is enabled.
* @return
*/
public boolean isDelayRebalanceEnabled() {
boolean disabled =
_record.getBooleanField(IdealStateProperty.DELAY_REBALANCE_DISABLED.name(), false);
boolean enabled =
_record.getBooleanField(ResourceConfigProperty.DELAY_REBALANCE_ENABLED.name(), true);
if (disabled) {
return false;
}
return enabled;
}
/**
* Get the resource group name
*
* @return
*/
public String getResourceGroupName() {
return _record.getSimpleField(IdealStateProperty.RESOURCE_GROUP_NAME.toString());
}
/**
* Get if the resource group routing feature is enabled or not
* By default, it's disabled
*
* @return true if enabled; false otherwise
*/
public boolean isResourceGroupEnabled() {
return _record.getBooleanField(IdealStateProperty.GROUP_ROUTING_ENABLED.name(), false);
}
/**
* Enable/Disable the aggregated routing on resource group.
*
* @param enabled
*/
public void enableGroupRouting(boolean enabled) {
_record.setSimpleField(IdealStateProperty.GROUP_ROUTING_ENABLED.name(),
Boolean.toString(enabled));
}
/**
* If the external view for this resource is disabled. by default, it is false.
*
* @return true if the external view should be disabled for this resource.
*/
public boolean isExternalViewDisabled() {
return _record.getBooleanField(IdealStateProperty.EXTERNAL_VIEW_DISABLED.name(), false);
}
/**
* Disable (true) or enable (false) External View for this resource.
*/
public void setDisableExternalView(boolean disableExternalView) {
_record
.setSimpleField(IdealStateProperty.EXTERNAL_VIEW_DISABLED.name(),
Boolean.toString(disableExternalView));
}
/**
* Set the maximum number of partitions of this resource that an instance can serve
* @param max the maximum number of partitions supported
*/
public void setMaxPartitionsPerInstance(int max) {
_record.setIntField(IdealStateProperty.MAX_PARTITIONS_PER_INSTANCE.toString(), max);
}
/**
* Get the rebalancing mode on this resource
* @return {@link IdealStateModeProperty}
*/
@Deprecated
public IdealStateModeProperty getIdealStateMode() {
return _record.getEnumField(IdealStateProperty.IDEAL_STATE_MODE.toString(),
IdealStateModeProperty.class, IdealStateModeProperty.AUTO);
}
/**
* Get the rebalancing mode on this resource
* @return {@link RebalanceMode}
*/
public RebalanceMode getRebalanceMode() {
RebalanceMode property =
_record.getEnumField(IdealStateProperty.REBALANCE_MODE.toString(), RebalanceMode.class,
RebalanceMode.NONE);
if (property == RebalanceMode.NONE) {
property = normalizeRebalanceMode(getIdealStateMode());
setRebalanceMode(property);
}
return property;
}
/**
* Set the preferred instance placement and state for a partition replica
* @param partitionName the replica to set
* @param instanceName the assigned instance
* @param state the replica state in this instance
*/
public void setPartitionState(String partitionName, String instanceName, String state) {
Map<String, String> mapField = _record.getMapField(partitionName);
if (mapField == null) {
_record.setMapField(partitionName, new TreeMap<String, String>());
}
_record.getMapField(partitionName).put(instanceName, state);
}
/**
* Get all of the partitions
* @return a set of partition names
*/
public Set<String> getPartitionSet() {
if (getRebalanceMode() == RebalanceMode.SEMI_AUTO
|| getRebalanceMode() == RebalanceMode.FULL_AUTO
|| getRebalanceMode() == RebalanceMode.USER_DEFINED
|| getRebalanceMode() == RebalanceMode.TASK) {
return _record.getListFields().keySet();
} else if (getRebalanceMode() == RebalanceMode.CUSTOMIZED) {
return _record.getMapFields().keySet();
} else {
logger.error("Invalid ideal state mode:" + getResourceName());
return Collections.emptySet();
}
}
/**
* Get the current mapping of a partition.
*
* CAUTION: In FULL-AUTO mode, this method
* could return empty map if neither {@link ClusterConfig#setPersistBestPossibleAssignment(Boolean)}
* nor {@link ClusterConfig#setPersistIntermediateAssignment(Boolean)} is set to true.
*
* @param partitionName the name of the partition
* @return the instances where the replicas live and the state of each
*/
public Map<String, String> getInstanceStateMap(String partitionName) {
return _record.getMapField(partitionName);
}
/**
* Set the current mapping of a partition
*
* @param partitionName the name of the partition
* @param instanceStateMap instance->state mapping for this partition.
* @return the instances where the replicas live and the state of each
*/
public void setInstanceStateMap(String partitionName, Map<String, String> instanceStateMap) {
_record.setMapField(partitionName, instanceStateMap);
}
/**
* Get the instances who host replicas of a partition.
* CAUTION: In FULL-AUTO mode, this method
* could return empty set if neither {@link ClusterConfig#setPersistBestPossibleAssignment(Boolean)}
* nor {@link ClusterConfig#setPersistIntermediateAssignment(Boolean)} is set to true.
*
* @return set of instance names
*/
public Set<String> getInstanceSet(String partitionName) {
switch(getRebalanceMode()) {
case FULL_AUTO:
case SEMI_AUTO:
case USER_DEFINED:
case TASK:
List<String> prefList = _record.getListField(partitionName);
if (prefList != null && !prefList.isEmpty()) {
return new TreeSet<String>(prefList);
} else {
Map<String, String> stateMap = _record.getMapField(partitionName);
if (stateMap != null && !stateMap.isEmpty()) {
return new TreeSet<String>(stateMap.keySet());
} else {
logger.warn(partitionName + " does NOT exist");
}
}
break;
case CUSTOMIZED:
Map<String, String> stateMap = _record.getMapField(partitionName);
if (stateMap != null) {
return new TreeSet<String>(stateMap.keySet());
} else {
logger.warn(partitionName + " does NOT exist");
}
break;
case NONE:
default:
logger.warn("Invalid ideal state mode: " + getResourceName());
break;
}
return Collections.emptySet();
}
/**
* Get the preference list of a partition
* @param partitionName the name of the partition
* @return a list of instances that can serve replicas of the partition
*/
public List<String> getPreferenceList(String partitionName) {
List<String> instanceStateList = _record.getListField(partitionName);
if (instanceStateList != null) {
return instanceStateList;
}
return null;
}
/**
* Set the preference list of a partition
*
* @param partitionName the name of the partition
* @param instanceList the instance preference list
*/
public void setPreferenceList(String partitionName, List<String> instanceList) {
_record.setListField(partitionName, instanceList);
}
/**
* Set the preference lists for all partitions in this resource.
*
* @param instanceLists the map of instance preference lists.
*/
public void setPreferenceLists(Map<String, List<String>> instanceLists) {
_record.setListFields(instanceLists);
}
/**
* Get the preference lists for all partitions
*
* @return map of lists of instances for all partitions in this resource.
*/
public Map<String, List<String>> getPreferenceLists() {
return _record.getListFields();
}
/**
* Get the state model associated with this resource
* @return an identifier of the state model
*/
public String getStateModelDefRef() {
return _record.getSimpleField(IdealStateProperty.STATE_MODEL_DEF_REF.toString());
}
/**
* Set the state model associated with this resource
* @param stateModel state model identifier
*/
public void setStateModelDefRef(String stateModel) {
_record.setSimpleField(IdealStateProperty.STATE_MODEL_DEF_REF.toString(), stateModel);
}
/**
* Set the number of partitions of this resource
* @param numPartitions the number of partitions
*/
public void setNumPartitions(int numPartitions) {
_record.setIntField(IdealStateProperty.NUM_PARTITIONS.toString(), numPartitions);
}
/**
* Get the number of partitions of this resource
* @return the number of partitions
*/
public int getNumPartitions() {
return _record.getIntField(IdealStateProperty.NUM_PARTITIONS.toString(), -1);
}
/**
* Set the number of minimal active partitions for this resource.
*
* @param minActiveReplicas
*/
public void setMinActiveReplicas(int minActiveReplicas) {
_record.setIntField(IdealStateProperty.MIN_ACTIVE_REPLICAS.toString(), minActiveReplicas);
}
/**
* Get the number of minimal active partitions for this resource.
*
* @return
*/
public int getMinActiveReplicas() {
return _record.getIntField(IdealStateProperty.MIN_ACTIVE_REPLICAS.toString(), -1);
}
/**
* Set the number of replicas for each partition of this resource. There are documented special
* values for the replica count, so this is a String.
* @param replicas replica count (as a string)
*/
public void setReplicas(String replicas) {
_record.setSimpleField(IdealStateProperty.REPLICAS.toString(), replicas);
}
/**
* Get the number of replicas for each partition of this resource
* @return number of replicas (as a string)
*/
public String getReplicas() {
// HACK: if replica doesn't exists, use the length of the first list field
// instead
// TODO: remove it when Dbus fixed the IdealState writer
// TODO: replica could be "ANY_INSTANCE".
String replica = _record.getSimpleField(IdealStateProperty.REPLICAS.toString());
if (replica == null) {
String firstPartition = null;
switch (getRebalanceMode()) {
case SEMI_AUTO:
if (_record.getListFields().size() == 0) {
replica = "0";
} else {
firstPartition = new ArrayList<String>(_record.getListFields().keySet()).get(0);
replica =
Integer.toString(firstPartition == null ? 0 : _record.getListField(firstPartition)
.size());
}
logger
.warn("could NOT find number of replicas in idealState. Use size of the first list instead. replica: "
+ replica + ", 1st partition: " + firstPartition);
break;
case CUSTOMIZED:
if (_record.getMapFields().size() == 0) {
replica = "0";
} else {
firstPartition = new ArrayList<String>(_record.getMapFields().keySet()).get(0);
replica =
Integer.toString(firstPartition == null ? 0 : _record.getMapField(firstPartition)
.size());
}
logger
.warn("could NOT find replicas in idealState. Use size of the first map instead. replica: "
+ replica + ", 1st partition: " + firstPartition);
break;
default:
replica = "0";
logger.error("could NOT determine replicas. set to 0");
break;
}
}
return replica;
}
/**
* Get the number of replicas for each partition of this resource
*
* @return number of replicas
*/
public int getReplicaCount(int eligibleInstancesCount) {
String replicaStr = getReplicas();
int replica = 0;
try {
replica = Integer.parseInt(replicaStr);
} catch (NumberFormatException ex) {
if (replicaStr
.equalsIgnoreCase(ResourceConfig.ResourceConfigConstants.ANY_LIVEINSTANCE.name())) {
replica = eligibleInstancesCount;
} else {
logger.error("Can not determine the replica count for resource " + getResourceName()
+ ", set to 0.");
}
}
return replica;
}
/**
* Set the state model factory associated with this resource
* @param name state model factory name
*/
public void setStateModelFactoryName(String name) {
_record.setSimpleField(IdealStateProperty.STATE_MODEL_FACTORY_NAME.toString(), name);
}
/**
* Get the state model factory associated with this resource
* @return state model factory name
*/
public String getStateModelFactoryName() {
return _record.getStringField(IdealStateProperty.STATE_MODEL_FACTORY_NAME.toString(),
HelixConstants.DEFAULT_STATE_MODEL_FACTORY);
}
/**
* Set the frequency with which to rebalance
* @return the rebalancing timer period
*/
public long getRebalanceTimerPeriod() {
return _record.getLongField(IdealStateProperty.REBALANCE_TIMER_PERIOD.toString(), -1);
}
@Override
public boolean isValid() {
if (getNumPartitions() < 0) {
logger.error("idealState:" + _record.getId() + " does not have number of partitions (was "
+ getNumPartitions() + ").");
return false;
}
if (getStateModelDefRef() == null) {
logger.error("idealStates:" + _record.getId() + " does not have state model definition.");
return false;
}
if (getRebalanceMode() == RebalanceMode.SEMI_AUTO) {
String replicaStr = getReplicas();
if (replicaStr == null) {
logger.error("invalid ideal-state. missing replicas in auto mode. record was: " + _record.getId());
return false;
}
if (!replicaStr.equals(IdealStateConstants.ANY_LIVEINSTANCE.toString())) {
int replica = Integer.parseInt(replicaStr);
Set<String> partitionSet = getPartitionSet();
for (String partition : partitionSet) {
List<String> preferenceList = getPreferenceList(partition);
if (preferenceList == null || preferenceList.size() != replica) {
logger
.error("invalid ideal-state. preference-list size not equals to replicas in auto mode. replica: "
+ replica
+ ", preference-list size: "
+ preferenceList.size()
+ ", record was: " + _record.getId());
return false;
}
}
}
}
return true;
}
/**
* Set a tag to check to enforce assignment to certain instances
* @param groupTag the instance group tag
*/
public void setInstanceGroupTag(String groupTag) {
_record.setSimpleField(IdealStateProperty.INSTANCE_GROUP_TAG.toString(), groupTag);
}
/**
* Check for a tag that will restrict assignment to instances with a matching tag
* @return the group tag, or null if none is present
*/
public String getInstanceGroupTag() {
return _record.getSimpleField(IdealStateProperty.INSTANCE_GROUP_TAG.toString());
}
private RebalanceMode normalizeRebalanceMode(IdealStateModeProperty mode) {
RebalanceMode property;
switch (mode) {
case AUTO_REBALANCE:
property = RebalanceMode.FULL_AUTO;
break;
case AUTO:
property = RebalanceMode.SEMI_AUTO;
break;
case CUSTOMIZED:
property = RebalanceMode.CUSTOMIZED;
break;
default:
String rebalancerName = getRebalancerClassName();
if (rebalancerName != null) {
if (rebalancerName.equals(JobRebalancer.class.getName())
|| rebalancerName.equals(WorkflowRebalancer.class.getName())
|| rebalancerName.equals(GenericTaskRebalancer.class.getName())
|| rebalancerName.equals(FixedTargetTaskRebalancer.class.getName())) {
property = RebalanceMode.TASK;
} else {
property = RebalanceMode.USER_DEFINED;
}
} else {
property = RebalanceMode.SEMI_AUTO;
}
break;
}
return property;
}
private IdealStateModeProperty denormalizeRebalanceMode(RebalanceMode rebalancerType) {
IdealStateModeProperty property;
switch (rebalancerType) {
case FULL_AUTO:
property = IdealStateModeProperty.AUTO_REBALANCE;
break;
case SEMI_AUTO:
property = IdealStateModeProperty.AUTO;
break;
case CUSTOMIZED:
property = IdealStateModeProperty.CUSTOMIZED;
break;
default:
property = IdealStateModeProperty.AUTO;
break;
}
return property;
}
/**
* Parse a RebalanceMode from a string. It can also understand IdealStateModeProperty values.
* @param mode string containing a RebalanceMode value
* @param defaultMode the mode to use if the string is not valid
* @return converted RebalanceMode value
*/
public RebalanceMode rebalanceModeFromString(String mode, RebalanceMode defaultMode) {
RebalanceMode rebalanceMode = defaultMode;
try {
rebalanceMode = RebalanceMode.valueOf(mode);
} catch (Exception rebalanceModeException) {
try {
IdealStateModeProperty oldMode = IdealStateModeProperty.valueOf(mode);
rebalanceMode = normalizeRebalanceMode(oldMode);
} catch (Exception e) {
logger.error(e.toString());
}
}
return rebalanceMode;
}
/**
* Get if the resource is enabled or not
* By default, it's enabled
* @return true if enabled; false otherwise
*/
public boolean isEnabled() {
return _record.getBooleanField(IdealStateProperty.HELIX_ENABLED.name(), true);
}
/**
* Enable/Disable the resource
* @param enabled
*/
public void enable(boolean enabled) {
_record.setSimpleField(IdealStateProperty.HELIX_ENABLED.name(), Boolean.toString(enabled));
}
}