use of org.apache.helix.model.InstanceConfig in project ambry by linkedin.
the class MockHelixDataAccessor method getProperty.
@Override
public <T extends HelixProperty> List<T> getProperty(List<PropertyKey> keys, boolean throwException) {
List<T> result = new ArrayList<>();
for (PropertyKey key : keys) {
if (key.toString().matches("/Ambry-/INSTANCES/.*/CURRENTSTATES/\\d+/\\d+")) {
// an example for the key: /Ambry-/INSTANCES/localhost_18089/CURRENTSTATES/sessionId/0
String[] segments = key.toString().split("/");
String instanceName = segments[3];
String resourceName = segments[6];
Map<String, Map<String, String>> partitionStateMap = mockHelixAdmin.getPartitionStateMapForInstance(instanceName);
ZNRecord record = new ZNRecord(resourceName);
record.setMapFields(partitionStateMap);
result.add((T) (new CurrentState(record)));
} else if (key.toString().matches("/Ambry-/LIVEINSTANCES/.*_\\d+")) {
String[] segments = key.toString().split("/");
String instanceName = segments[3];
ZNRecord record = new ZNRecord(instanceName);
record.setEphemeralOwner(SESSION_ID);
result.add((T) (new LiveInstance(record)));
} else if (key.toString().matches("/Ambry-/CONFIGS/PARTICIPANT/.*_\\d+")) {
String[] segments = key.toString().split("/");
String instanceName = segments[4];
InstanceConfig instanceConfig = mockHelixAdmin.getInstanceConfigs(clusterName).stream().filter(config -> config.getInstanceName().equals(instanceName)).findFirst().get();
result.add((T) instanceConfig);
} else {
result.add((T) properties.get(key));
}
}
return result;
}
use of org.apache.helix.model.InstanceConfig in project ambry by linkedin.
the class HelixBootstrapUpgradeUtil method addUpdateResources.
/**
* Add and/or update resources in Helix based on the information in the static cluster map. This may involve adding
* or removing partitions from under a resource, and adding or dropping resources altogether. This may also involve
* changing the instance set for a partition under a resource, based on the static cluster map.
* @param dcName the name of the datacenter being processed.
* @param partitionsToInstancesInDc a map to be filled with the mapping of partitions to their instance sets in the
* given datacenter.
*/
private void addUpdateResources(String dcName, Map<String, Set<String>> partitionsToInstancesInDc) {
HelixAdmin dcAdmin = adminForDc.get(dcName);
List<String> resourcesInCluster = dcAdmin.getResourcesInCluster(clusterName);
List<String> instancesWithDisabledPartition = new ArrayList<>();
HelixPropertyStore<ZNRecord> helixPropertyStore = helixAdminOperation == HelixAdminOperation.DisablePartition ? createHelixPropertyStore(dcName) : null;
// maxResource may vary from one dc to another (special partition class allows partitions to exist in one dc only)
int maxResource = -1;
for (String resourceName : resourcesInCluster) {
boolean resourceModified = false;
if (!resourceName.matches("\\d+")) {
// cluster map. These will be ignored.
continue;
}
maxResource = Math.max(maxResource, Integer.parseInt(resourceName));
IdealState resourceIs = dcAdmin.getResourceIdealState(clusterName, resourceName);
for (String partitionName : new HashSet<>(resourceIs.getPartitionSet())) {
Set<String> instanceSetInHelix = resourceIs.getInstanceSet(partitionName);
Set<String> instanceSetInStatic = partitionsToInstancesInDc.remove(partitionName);
if (instanceSetInStatic == null || instanceSetInStatic.isEmpty()) {
if (forceRemove) {
info("[{}] *** Partition {} no longer present in the static clustermap, {} *** ", dcName.toUpperCase(), partitionName, dryRun ? "no action as dry run" : "removing from Resource");
// Helix team is planning to provide an API for this.
if (!dryRun) {
resourceIs.getRecord().getListFields().remove(partitionName);
}
resourceModified = true;
} else {
info("[{}] *** forceRemove option not provided, resources will not be removed (use --forceRemove to forcefully remove)", dcName.toUpperCase());
expectMoreInHelixDuringValidate = true;
partitionsNotForceRemovedByDc.computeIfAbsent(dcName, k -> ConcurrentHashMap.newKeySet()).add(partitionName);
}
} else if (!instanceSetInStatic.equals(instanceSetInHelix)) {
// we change the IdealState only when the operation is meant to bootstrap cluster or indeed update IdealState
if (EnumSet.of(HelixAdminOperation.UpdateIdealState, HelixAdminOperation.BootstrapCluster).contains(helixAdminOperation)) {
// @formatter:off
info("[{}] Different instance sets for partition {} under resource {}. {}. " + "Previous instance set: [{}], new instance set: [{}]", dcName.toUpperCase(), partitionName, resourceName, dryRun ? "No action as dry run" : "Updating Helix using static", String.join(",", instanceSetInHelix), String.join(",", instanceSetInStatic));
// @formatter:on
if (!dryRun) {
ArrayList<String> newInstances = new ArrayList<>(instanceSetInStatic);
Collections.shuffle(newInstances);
resourceIs.setPreferenceList(partitionName, newInstances);
// Existing resources may not have ANY_LIVEINSTANCE set as the numReplicas (which allows for different
// replication for different partitions under the same resource). So set it here (We use the name() method and
// not the toString() method for the enum as that is what Helix uses).
resourceIs.setReplicas(ResourceConfig.ResourceConfigConstants.ANY_LIVEINSTANCE.name());
}
resourceModified = true;
} else if (helixAdminOperation == HelixAdminOperation.DisablePartition) {
// if this is DisablePartition operation, we don't modify IdealState and only make InstanceConfig to disable
// certain partition on specific node.
// 1. extract difference between Helix and Static instance sets. Determine which replica is removed
instanceSetInHelix.removeAll(instanceSetInStatic);
// 2. disable removed replica on certain node.
for (String instanceInHelixOnly : instanceSetInHelix) {
info("Partition {} under resource {} on node {} is no longer in static clustermap. {}.", partitionName, resourceName, instanceInHelixOnly, dryRun ? "No action as dry run" : "Disabling it");
if (!dryRun) {
InstanceConfig instanceConfig = dcAdmin.getInstanceConfig(clusterName, instanceInHelixOnly);
String instanceName = instanceConfig.getInstanceName();
// on updating InstanceConfig until this tool has completed. TODO, remove this logic once migration to PropertyStore is done.
if (!instancesWithDisabledPartition.contains(instanceName)) {
ZNRecord znRecord = new ZNRecord(instanceName);
String path = PARTITION_DISABLED_ZNODE_PATH + instanceName;
if (!helixPropertyStore.create(path, znRecord, AccessOption.PERSISTENT)) {
logger.error("Failed to create a ZNode for {} in datacenter {} before disabling partition.", instanceName, dcName);
continue;
}
}
instanceConfig.setInstanceEnabledForPartition(resourceName, partitionName, false);
dcAdmin.setInstanceConfig(clusterName, instanceInHelixOnly, instanceConfig);
instancesWithDisabledPartition.add(instanceName);
}
partitionsDisabled.getAndIncrement();
}
// Disabling partition won't remove certain replica from IdealState. So replicas of this partition in Helix
// will be more than that in static clustermap.
expectMoreInHelixDuringValidate = true;
}
}
}
// update state model def if necessary
if (!resourceIs.getStateModelDefRef().equals(stateModelDef)) {
info("[{}] Resource {} has different state model {}. Updating it with {}", dcName.toUpperCase(), resourceName, resourceIs.getStateModelDefRef(), stateModelDef);
resourceIs.setStateModelDefRef(stateModelDef);
resourceModified = true;
}
resourceIs.setNumPartitions(resourceIs.getPartitionSet().size());
if (resourceModified) {
if (resourceIs.getPartitionSet().isEmpty()) {
info("[{}] Resource {} has no partition, {}", dcName.toUpperCase(), resourceName, dryRun ? "no action as dry run" : "dropping");
if (!dryRun) {
dcAdmin.dropResource(clusterName, resourceName);
}
resourcesDropped.getAndIncrement();
} else {
if (!dryRun) {
dcAdmin.setResourceIdealState(clusterName, resourceName, resourceIs);
System.out.println("------------------add resource!");
System.out.println(resourceName);
System.out.println(resourceName);
}
resourcesUpdated.getAndIncrement();
}
}
}
// note that disabling partition also updates InstanceConfig of certain nodes which host the partitions.
instancesUpdated.getAndAdd(instancesWithDisabledPartition.size());
// replica decommission thread on each datanode.
if (helixPropertyStore != null) {
maybeAwaitForLatch();
for (String instanceName : instancesWithDisabledPartition) {
String path = PARTITION_DISABLED_ZNODE_PATH + instanceName;
if (!helixPropertyStore.remove(path, AccessOption.PERSISTENT)) {
logger.error("Failed to remove a ZNode for {} in datacenter {} after disabling partition completed.", instanceName, dcName);
}
}
helixPropertyStore.stop();
}
// Add what is not already in Helix under new resources.
int fromIndex = 0;
List<Map.Entry<String, Set<String>>> newPartitions = new ArrayList<>(partitionsToInstancesInDc.entrySet());
while (fromIndex < newPartitions.size()) {
String resourceName = Integer.toString(++maxResource);
int toIndex = Math.min(fromIndex + maxPartitionsInOneResource, newPartitions.size());
List<Map.Entry<String, Set<String>>> partitionsUnderNextResource = newPartitions.subList(fromIndex, toIndex);
fromIndex = toIndex;
IdealState idealState = new IdealState(resourceName);
idealState.setStateModelDefRef(stateModelDef);
info("[{}] Adding partitions for next resource {} in {}. {}.", dcName.toUpperCase(), resourceName, dcName, dryRun ? "Actual IdealState is not changed as dry run" : "IdealState is being updated");
for (Map.Entry<String, Set<String>> entry : partitionsUnderNextResource) {
String partitionName = entry.getKey();
ArrayList<String> instances = new ArrayList<>(entry.getValue());
Collections.shuffle(instances);
idealState.setPreferenceList(partitionName, instances);
}
idealState.setNumPartitions(partitionsUnderNextResource.size());
idealState.setReplicas(ResourceConfig.ResourceConfigConstants.ANY_LIVEINSTANCE.name());
if (!idealState.isValid()) {
throw new IllegalStateException("IdealState could not be validated for new resource " + resourceName);
}
if (!dryRun) {
dcAdmin.addResource(clusterName, resourceName, idealState);
info("[{}] Added {} new partitions under resource {} in datacenter {}", dcName.toUpperCase(), partitionsUnderNextResource.size(), resourceName, dcName);
} else {
info("[{}] Under DryRun mode, {} new partitions are added to resource {} in datacenter {}", dcName.toUpperCase(), partitionsUnderNextResource.size(), resourceName, dcName);
}
resourcesAdded.getAndIncrement();
}
}
use of org.apache.helix.model.InstanceConfig in project ambry by linkedin.
the class HelixBootstrapUpgradeUtil method createInstanceConfigFromStaticInfo.
/**
* Create an {@link InstanceConfig} for the given node from the static cluster information.
* @param node the {@link DataNodeId}
* @param partitionToInstances the map of partitions to instances that will be populated for this instance.
* @param instanceToDiskReplicasMap the map of instances to the map of disk to set of replicas.
* @param referenceInstanceConfig the InstanceConfig used to set the fields that are not derived from the json files.
* These are the SEALED state and STOPPED_REPLICAS configurations. If this field is null,
* then these fields are derived from the json files. This can happen if this is a newly
* added node.
* @return the constructed {@link InstanceConfig}
*/
static InstanceConfig createInstanceConfigFromStaticInfo(DataNodeId node, Map<String, Set<String>> partitionToInstances, ConcurrentHashMap<String, Map<DiskId, SortedSet<Replica>>> instanceToDiskReplicasMap, InstanceConfig referenceInstanceConfig) {
String instanceName = getInstanceName(node);
InstanceConfig instanceConfig = new InstanceConfig(instanceName);
instanceConfig.setHostName(node.getHostname());
instanceConfig.setPort(Integer.toString(node.getPort()));
if (node.hasSSLPort()) {
instanceConfig.getRecord().setSimpleField(SSL_PORT_STR, Integer.toString(node.getSSLPort()));
}
if (node.hasHttp2Port()) {
instanceConfig.getRecord().setSimpleField(HTTP2_PORT_STR, Integer.toString(node.getHttp2Port()));
}
instanceConfig.getRecord().setSimpleField(DATACENTER_STR, node.getDatacenterName());
instanceConfig.getRecord().setSimpleField(RACKID_STR, node.getRackId());
long xid = node.getXid();
if (xid != DEFAULT_XID) {
// Set the XID only if it is not the default, in order to avoid unnecessary updates.
instanceConfig.getRecord().setSimpleField(XID_STR, Long.toString(node.getXid()));
}
instanceConfig.getRecord().setSimpleField(SCHEMA_VERSION_STR, Integer.toString(CURRENT_SCHEMA_VERSION));
List<String> sealedPartitionsList = new ArrayList<>();
List<String> stoppedReplicasList = new ArrayList<>();
List<String> disabledReplicasList = new ArrayList<>();
if (instanceToDiskReplicasMap.containsKey(instanceName)) {
Map<String, Map<String, String>> diskInfos = new TreeMap<>();
for (HashMap.Entry<DiskId, SortedSet<Replica>> diskToReplicas : instanceToDiskReplicasMap.get(instanceName).entrySet()) {
DiskId disk = diskToReplicas.getKey();
SortedSet<Replica> replicasInDisk = diskToReplicas.getValue();
// Note: An instance config has to contain the information for each disk about the replicas it hosts.
// This information will be initialized to the empty string - but will be updated whenever the partition
// is added to the cluster.
StringBuilder replicasStrBuilder = new StringBuilder();
for (ReplicaId replicaId : replicasInDisk) {
Replica replica = (Replica) replicaId;
replicasStrBuilder.append(replica.getPartition().getId()).append(REPLICAS_STR_SEPARATOR).append(replica.getCapacityInBytes()).append(REPLICAS_STR_SEPARATOR).append(replica.getPartition().getPartitionClass()).append(REPLICAS_DELIM_STR);
if (referenceInstanceConfig == null && replica.isSealed()) {
sealedPartitionsList.add(Long.toString(replica.getPartition().getId()));
}
partitionToInstances.computeIfAbsent(Long.toString(replica.getPartition().getId()), k -> new HashSet<>()).add(instanceName);
}
Map<String, String> diskInfo = new HashMap<>();
diskInfo.put(DISK_CAPACITY_STR, Long.toString(disk.getRawCapacityInBytes()));
diskInfo.put(DISK_STATE, AVAILABLE_STR);
diskInfo.put(REPLICAS_STR, replicasStrBuilder.toString());
diskInfos.put(disk.getMountPath(), diskInfo);
}
instanceConfig.getRecord().setMapFields(diskInfos);
}
// Set the fields that need to be preserved from the referenceInstanceConfig.
if (referenceInstanceConfig != null) {
sealedPartitionsList = ClusterMapUtils.getSealedReplicas(referenceInstanceConfig);
stoppedReplicasList = ClusterMapUtils.getStoppedReplicas(referenceInstanceConfig);
disabledReplicasList = ClusterMapUtils.getDisabledReplicas(referenceInstanceConfig);
}
instanceConfig.getRecord().setListField(SEALED_STR, sealedPartitionsList);
instanceConfig.getRecord().setListField(STOPPED_REPLICAS_STR, stoppedReplicasList);
instanceConfig.getRecord().setListField(DISABLED_REPLICAS_STR, disabledReplicasList);
return instanceConfig;
}
use of org.apache.helix.model.InstanceConfig in project ambry by linkedin.
the class HelixClusterManagerTest method xidTest.
/**
* Tests that if the xid of an InstanceConfig change is greater than the current xid of the cluster manager, then that
* change is ignored - both during initialization as well as with post-initialization InstanceConfig changes.
*/
@Test
public void xidTest() throws Exception {
assumeTrue(!useComposite && listenCrossColo);
// Close the one initialized in the constructor, as this test needs to test initialization flow as well.
clusterManager.close();
// Initialization path:
MockHelixManagerFactory helixManagerFactory = new MockHelixManagerFactory(helixCluster, null, null);
List<InstanceConfig> instanceConfigs = helixCluster.getInstanceConfigsFromDcs(helixDcs);
int instanceCount = instanceConfigs.size();
// find the self instance config and put it at the end of list. This is to ensure subsequent test won't choose self instance config.
for (int i = 0; i < instanceCount; ++i) {
if (instanceConfigs.get(i).getInstanceName().equals(selfInstanceName)) {
Collections.swap(instanceConfigs, i, instanceConfigs.size() - 1);
break;
}
}
int randomIndex = com.github.ambry.utils.TestUtils.RANDOM.nextInt(instanceCount - 1);
InstanceConfig aheadInstanceConfig = instanceConfigs.get(randomIndex);
Collections.swap(instanceConfigs, randomIndex, instanceConfigs.size() - 2);
aheadInstanceConfig.getRecord().setSimpleField(XID_STR, Long.toString(CURRENT_XID + 1));
clusterManager = new HelixClusterManager(clusterMapConfig, selfInstanceName, helixManagerFactory, new MetricRegistry());
assertEquals(instanceCount + numCloudDcs - 1, clusterManager.getDataNodeIds().size());
for (DataNodeId dataNode : clusterManager.getDataNodeIds()) {
String instanceName = ClusterMapUtils.getInstanceName(dataNode.getHostname(), dataNode.getPort());
assertThat(instanceName, not(aheadInstanceConfig.getInstanceName()));
}
// Ahead instance should be honored if the cluster manager is of the aheadInstance.
try (HelixClusterManager aheadInstanceClusterManager = new HelixClusterManager(clusterMapConfig, aheadInstanceConfig.getInstanceName(), helixManagerFactory, new MetricRegistry())) {
assertEquals(instanceCount + numCloudDcs, aheadInstanceClusterManager.getDataNodeIds().size());
}
// Post-initialization InstanceConfig change: pick an instance that is neither previous instance nor self instance
InstanceConfig ignoreInstanceConfig = instanceConfigs.get(com.github.ambry.utils.TestUtils.RANDOM.nextInt(instanceCount - 2));
String ignoreInstanceName = ignoreInstanceConfig.getInstanceName();
ignoreInstanceConfig.getRecord().setSimpleField(XID_STR, Long.toString(CURRENT_XID + 2));
AmbryReplica ignoreInstanceReplica = null;
for (DataNodeId dataNode : clusterManager.getDataNodeIds()) {
String instanceName = ClusterMapUtils.getInstanceName(dataNode.getHostname(), dataNode.getPort());
if (instanceName.equals(ignoreInstanceName)) {
ignoreInstanceReplica = (AmbryReplica) clusterManager.getReplicaIds(dataNode).get(0);
ignoreInstanceConfig.getRecord().setListField(STOPPED_REPLICAS_STR, Collections.singletonList(ignoreInstanceReplica.getPartitionId().toPathString()));
break;
}
}
helixCluster.triggerInstanceConfigChangeNotification();
// Because the XID was higher, the change reflecting this replica being stopped will not be absorbed.
assertFalse(ignoreInstanceReplica.isDown());
// Now advance the current xid of the cluster manager (simulated by moving back the xid in the InstanceConfig).
ignoreInstanceConfig.getRecord().setSimpleField(XID_STR, Long.toString(CURRENT_XID - 2));
helixCluster.triggerInstanceConfigChangeNotification();
// Now the change should get absorbed.
assertTrue(ignoreInstanceReplica.isDown());
}
use of org.apache.helix.model.InstanceConfig in project ambry by linkedin.
the class HelixClusterManagerTest method listenCrossColoTest.
/**
* Ensure that effects of the listenCrossColo config is as expected. When it is set to false, the Helix cluster manager
* initializes fine, but listens to subsequent InstanceConfig changes in the local colo only.
*/
@Test
public void listenCrossColoTest() {
assumeTrue(!useComposite);
HelixClusterManager helixClusterManager = (HelixClusterManager) clusterManager;
Counter instanceTriggerCounter = helixClusterManager.helixClusterManagerMetrics.instanceConfigChangeTriggerCount;
Map<String, DcInfo> dcInfosMap = helixClusterManager.getDcInfosMap();
Map<String, HelixManager> helixManagerMap = dcInfosMap.entrySet().stream().filter(e -> e.getValue() instanceof HelixDcInfo).collect(Collectors.toMap(Map.Entry::getKey, e -> ((HelixDcInfo) e.getValue()).helixManager));
for (Map.Entry<String, HelixManager> entry : helixManagerMap.entrySet()) {
if (entry.getKey().equals(localDc)) {
assertTrue("Helix cluster manager should always be connected to the local Helix manager", entry.getValue().isConnected());
} else {
assertEquals("Helix cluster manager should be connected to the remote Helix managers if and only if listenCrossColo is" + "set to true", listenCrossColo, entry.getValue().isConnected());
}
}
long instanceConfigChangeTriggerCount = instanceTriggerCounter.getCount();
helixCluster.triggerInstanceConfigChangeNotification();
assertEquals("Number of trigger count should be in accordance to listenCrossColo value", instanceConfigChangeTriggerCount + (listenCrossColo ? helixDcs.length : 1), instanceTriggerCounter.getCount());
InstanceConfig remoteInstanceConfig = helixCluster.getInstanceConfigsFromDcs(helixDcs).stream().filter(e -> ClusterMapUtils.getDcName(e).equals(remoteDc)).findAny().get();
DataNodeId remote = helixClusterManager.getDataNodeId(remoteInstanceConfig.getHostName(), Integer.parseInt(remoteInstanceConfig.getPort()));
Set<PartitionId> writablePartitions = new HashSet<>(helixClusterManager.getWritablePartitionIds(null));
PartitionId partitionIdToSealInRemote = helixClusterManager.getReplicaIds(remote).stream().filter(e -> writablePartitions.contains(e.getPartitionId())).findAny().get().getPartitionId();
remoteInstanceConfig.getRecord().setListField(SEALED_STR, Collections.singletonList(partitionIdToSealInRemote.toPathString()));
helixCluster.triggerInstanceConfigChangeNotification();
assertEquals("If replica in remote is sealed, partition should be sealed if and only if listenCrossColo is true " + "and override is disabled", !listenCrossColo || overrideEnabled, helixClusterManager.getWritablePartitionIds(null).contains(partitionIdToSealInRemote));
}
Aggregations