use of org.apache.helix.zookeeper.datamodel.ZNRecord in project ambry by linkedin.
the class HelixClusterManagerTest method clusterMapOverrideEnabledAndDisabledTest.
/**
* Test that ClusterManger will use seal state in PartitionOverride/InstanceConfig when {@link ClusterMapConfig#clusterMapEnablePartitionOverride}
* is enabled/disabled. This test verifies that InstanceConfig changes won't affect any seal state of partition if clusterMapEnablePartitionOverride
* is enabled. It also tests seal state can be dynamically changed by InstanceConfig change when PartitionOverride is
* non-empty but disabled.
*/
@Test
public void clusterMapOverrideEnabledAndDisabledTest() throws Exception {
assumeTrue(!useComposite && listenCrossColo);
// Get the writable partitions in OverrideMap
Set<String> writableInOverrideMap = new HashSet<>();
for (Map.Entry<String, Map<String, String>> entry : partitionOverrideMap.entrySet()) {
if (entry.getValue().get(ClusterMapUtils.PARTITION_STATE).equals(ClusterMapUtils.READ_WRITE_STR)) {
writableInOverrideMap.add(entry.getKey());
}
}
// Get the writable partitions in InstanceConfig(PartitionLayout)
List<PartitionId> writableInLayout = testPartitionLayout.getPartitionLayout().getWritablePartitions(null);
Set<String> writableInInstanceConfig = new HashSet<>();
writableInLayout.forEach(k -> writableInInstanceConfig.add(k.toPathString()));
if (overrideEnabled) {
// Verify clustermap uses partition override for initialization
Set<String> writableInClusterManager = getWritablePartitions().getSecond();
assertEquals("Mismatch in writable partitions during initialization", writableInOverrideMap, writableInClusterManager);
// Ensure clustermap ignores the InstanceConfig when override is enabled.
assertFalse("Writable partitions in ClusterManager should not equal to those in InstanceConfigs when override is enabled", writableInClusterManager.equals(writableInInstanceConfig));
// Verify writable partitions in clustermap remain unchanged when instanceConfig changes in Helix cluster
AmbryPartition partition = (AmbryPartition) clusterManager.getWritablePartitionIds(null).get(0);
List<String> instances = helixCluster.getInstancesForPartition((partition.toPathString()));
Counter instanceTriggerCounter = ((HelixClusterManager) clusterManager).helixClusterManagerMetrics.instanceConfigChangeTriggerCount;
long countVal = instanceTriggerCounter.getCount();
helixCluster.setReplicaState(partition, instances.get(0), ReplicaStateType.SealedState, true, false);
assertEquals("Mismatch in instanceTriggerCounter", countVal + 1, instanceTriggerCounter.getCount());
writableInClusterManager = getWritablePartitions().getSecond();
assertEquals("Mismatch in writable partitions when instanceConfig changes", writableInOverrideMap, writableInClusterManager);
// Verify the partition state could be changed if this partition is not in partition override map.
// Following test re-initializes clusterManager with new partitionLayout and then triggers instanceConfig change on new added partition
testPartitionLayout.addNewPartitions(1, DEFAULT_PARTITION_CLASS, PartitionState.READ_WRITE, helixDcs[0]);
Utils.writeJsonObjectToFile(testPartitionLayout.getPartitionLayout().toJSONObject(), partitionLayoutPath);
helixCluster.upgradeWithNewPartitionLayout(partitionLayoutPath, HelixBootstrapUpgradeUtil.HelixAdminOperation.BootstrapCluster);
clusterManager.close();
Map<String, ZNRecord> znRecordMap = new HashMap<>();
znRecordMap.put(PARTITION_OVERRIDE_ZNODE_PATH, znRecord);
MockHelixManagerFactory helixManagerFactory = new MockHelixManagerFactory(helixCluster, znRecordMap, null);
HelixClusterManager clusterManager = new HelixClusterManager(clusterMapConfig, selfInstanceName, helixManagerFactory, new MetricRegistry());
// Ensure the new RW partition is added
assertEquals("Mismatch in writable partitions when instanceConfig changes", writableInOverrideMap.size() + 1, clusterManager.getWritablePartitionIds(null).size());
// Find out the new added partition which is not in partition override map
for (PartitionId partitionId : clusterManager.getAllPartitionIds(null)) {
if (partitionId.toPathString().equals(String.valueOf(testPartitionLayout.getPartitionCount() - 1))) {
partition = (AmbryPartition) partitionId;
}
}
instances = helixCluster.getInstancesForPartition((partition.toPathString()));
// Change the replica from RW to RO, which triggers instanceConfig change
helixCluster.setReplicaState(partition, instances.get(0), ReplicaStateType.SealedState, true, false);
// Ensure the partition state becomes Read_Only
assertFalse("If any one replica is SEALED, the whole partition should be SEALED", clusterManager.getWritablePartitionIds(null).contains(partition));
assertEquals("If any one replica is SEALED, the whole partition should be SEALED", PartitionState.READ_ONLY, partition.getPartitionState());
} else {
// Verify clustermap uses instanceConfig for initialization when override map is non-empty but disabled.
Set<String> writableInClusterManager = getWritablePartitions().getSecond();
assertEquals("Mismatch in writable partitions during initialization", writableInInstanceConfig, writableInClusterManager);
// Ensure clustermap ignores partition override map when override is disabled.
assertFalse("Writable partitions in ClusterManager should not equal to those in OverrideMap when override is disabled", writableInClusterManager.equals(writableInOverrideMap));
// Verify partition state in clustermap is changed when instanceConfig changes in Helix cluster.
// This is to ensure partition override doesn't take any effect when it is disabled.
AmbryPartition partition = (AmbryPartition) clusterManager.getWritablePartitionIds(null).get(0);
List<String> instances = helixCluster.getInstancesForPartition((partition.toPathString()));
helixCluster.setReplicaState(partition, instances.get(0), ReplicaStateType.SealedState, true, false);
assertFalse("If any one replica is SEALED, the whole partition should be SEALED", clusterManager.getWritablePartitionIds(null).contains(partition));
assertEquals("If any one replica is SEALED, the whole partition should be SEALED", PartitionState.READ_ONLY, partition.getPartitionState());
// Ensure that after instanceConfig changes, the writable partitions in clusterManager match those in InstanceConfig
writableInInstanceConfig.remove(partition.toPathString());
writableInClusterManager = getWritablePartitions().getSecond();
assertEquals("Mismatch in writable partitions during initialization", writableInInstanceConfig, writableInClusterManager);
}
}
use of org.apache.helix.zookeeper.datamodel.ZNRecord in project ambry by linkedin.
the class HelixClusterManagerTest method getNewReplicaTest.
/**
* Test HelixClusterManager can get a new replica with given partitionId, hostname and port number.
* @throws Exception
*/
@Test
public void getNewReplicaTest() throws Exception {
assumeTrue(!useComposite);
clusterManager.close();
metricRegistry = new MetricRegistry();
// 1. test the case where ZNRecord is NULL in Helix PropertyStore
HelixClusterManager helixClusterManager = new HelixClusterManager(clusterMapConfig, selfInstanceName, new MockHelixManagerFactory(helixCluster, null, null), metricRegistry);
PartitionId partitionOfNewReplica = helixClusterManager.getAllPartitionIds(null).get(0);
DataNode dataNodeOfNewReplica = currentNode;
assertNull("New replica should be null because no replica infos ZNRecord in Helix", helixClusterManager.getBootstrapReplica(partitionOfNewReplica.toPathString(), dataNodeOfNewReplica));
helixClusterManager.close();
// Prepare new replica info map in Helix property store. We use the first partition in PartitionLayout and current
// node from constructor to build the replica info map of an existing partition. Also, we place a new partition on
// last data node in HardwareLayout. The format should be as follows.
// {
// "0":{
// "partitionClass" : "defaultPartitionClass"
// "replicaCapacityInBytes" : "107374182400"
// "localhost_18088" : "/mnt0"
// }
// "100":{
// "partitionClass" : "defaultPartitionClass"
// "replicaCapacityInBytes" : "107374182400"
// "localhost_18099" : "/mnt5"
// "localhost_18088" : "/mnt10"
// "newhost_001" : "/mnt0"
// }
// }
Map<String, Map<String, String>> partitionToReplicaInfosMap = new HashMap<>();
// new replica of existing partition
Map<String, String> newReplicaInfos1 = new HashMap<>();
newReplicaInfos1.put(PARTITION_CLASS_STR, DEFAULT_PARTITION_CLASS);
newReplicaInfos1.put(REPLICAS_CAPACITY_STR, String.valueOf(TestPartitionLayout.defaultReplicaCapacityInBytes));
newReplicaInfos1.put(dataNodeOfNewReplica.getHostname() + "_" + dataNodeOfNewReplica.getPort(), dataNodeOfNewReplica.getDisks().get(0).getMountPath());
partitionToReplicaInfosMap.put(partitionOfNewReplica.toPathString(), newReplicaInfos1);
// new replica of a new partition
Map<String, String> newReplicaInfos2 = new HashMap<>();
newReplicaInfos2.put(PARTITION_CLASS_STR, DEFAULT_PARTITION_CLASS);
newReplicaInfos2.put(REPLICAS_CAPACITY_STR, String.valueOf(TestPartitionLayout.defaultReplicaCapacityInBytes));
// find a node that is different from currentNode
DataNode dataNodeOfNewPartition = testHardwareLayout.getAllExistingDataNodes().stream().filter(node -> node.getPort() != currentNode.getPort()).findFirst().get();
int diskNum = dataNodeOfNewPartition.getDisks().size();
newReplicaInfos2.put(dataNodeOfNewPartition.getHostname() + "_" + dataNodeOfNewPartition.getPort(), dataNodeOfNewPartition.getDisks().get(diskNum - 1).getMountPath());
// add two fake entries (fake disk and fake host)
newReplicaInfos2.put(getInstanceName(dataNodeOfNewReplica.getHostname(), dataNodeOfNewReplica.getPort()), "/mnt10");
newReplicaInfos2.put("newhost_100", "/mnt0");
partitionToReplicaInfosMap.put(NEW_PARTITION_ID_STR, newReplicaInfos2);
// fake node that doesn't exist in current clustermap
DataNodeId fakeNode = Mockito.mock(DataNode.class);
Mockito.when(fakeNode.getHostname()).thenReturn("new_host");
Mockito.when(fakeNode.getPort()).thenReturn(100);
// set ZNRecord
ZNRecord replicaInfosZNRecord = new ZNRecord(REPLICA_ADDITION_STR);
replicaInfosZNRecord.setMapFields(partitionToReplicaInfosMap);
// populate znRecordMap
Map<String, ZNRecord> znRecordMap = new HashMap<>();
znRecordMap.put(REPLICA_ADDITION_ZNODE_PATH, replicaInfosZNRecord);
// create a new cluster manager
metricRegistry = new MetricRegistry();
helixClusterManager = new HelixClusterManager(clusterMapConfig, selfInstanceName, new MockHelixManagerFactory(helixCluster, znRecordMap, null), metricRegistry);
// 2. test that cases: 1) partition is not found 2) host is not found in helix property store that associates with new replica
// select a partition that doesn't equal to partitionOfNewReplica
PartitionId partitionForTest;
Random random = new Random();
List<PartitionId> partitionsInClusterManager = helixClusterManager.getAllPartitionIds(null);
do {
partitionForTest = partitionsInClusterManager.get(random.nextInt(partitionsInClusterManager.size()));
} while (partitionForTest.toString().equals(partitionOfNewReplica.toString()));
assertNull("New replica should be null because given partition id is not in Helix property store", helixClusterManager.getBootstrapReplica(partitionForTest.toPathString(), dataNodeOfNewReplica));
// select partitionOfNewReplica but a random node that doesn't equal to
DataNode dataNodeForTest;
List<DataNode> dataNodesInHardwareLayout = testHardwareLayout.getAllExistingDataNodes();
do {
dataNodeForTest = dataNodesInHardwareLayout.get(random.nextInt(dataNodesInHardwareLayout.size()));
} while (dataNodeForTest == dataNodeOfNewReplica);
assertNull("New replica should be null because hostname is not found in replica info map", helixClusterManager.getBootstrapReplica(partitionOfNewReplica.toPathString(), dataNodeForTest));
// 3. test that new replica is from a new partition which doesn't exist in current cluster yet. (Mock adding new partition case)
// 3.1 test new partition on host that is not present in current clustermap
assertNull("New replica should be null because host is not present in clustermap", helixClusterManager.getBootstrapReplica(NEW_PARTITION_ID_STR, fakeNode));
// 3.2 test new partition on disk that doesn't exist
assertNull("New replica should be null because disk doesn't exist", helixClusterManager.getBootstrapReplica(NEW_PARTITION_ID_STR, dataNodeOfNewReplica));
// 3.3 test replica is created for new partition
assertNotNull("New replica should be created successfully", helixClusterManager.getBootstrapReplica(NEW_PARTITION_ID_STR, dataNodeOfNewPartition));
// verify that boostrap replica map is empty because recently added replica is not on current node
assertTrue("Bootstrap replica map should be empty", helixClusterManager.getBootstrapReplicaMap().isEmpty());
// 4. test that new replica of existing partition is successfully created based on infos from Helix property store.
assertNotNull("New replica should be created successfully", helixClusterManager.getBootstrapReplica(partitionOfNewReplica.toPathString(), dataNodeOfNewReplica));
assertEquals("There should be exactly one entry in bootstrap replica map", 1, helixClusterManager.getBootstrapReplicaMap().size());
helixClusterManager.close();
}
use of org.apache.helix.zookeeper.datamodel.ZNRecord in project ambry by linkedin.
the class HelixParticipant method awaitDisablingPartition.
/**
* Wait until disabling partition process has completed. This is to avoid race condition where server and Helix may
* modify same InstanceConfig.
* TODO remove this method after migrating ambry to PropertyStore (in Helix).
* @throws InterruptedException
*/
private void awaitDisablingPartition() throws InterruptedException {
Properties properties = new Properties();
properties.setProperty("helix.property.store.root.path", "/" + clusterName + "/" + PROPERTYSTORE_STR);
HelixPropertyStoreConfig propertyStoreConfig = new HelixPropertyStoreConfig(new VerifiableProperties(properties));
HelixPropertyStore<ZNRecord> helixPropertyStore = CommonUtils.createHelixPropertyStore(zkConnectStr, propertyStoreConfig, null);
String path = PARTITION_DISABLED_ZNODE_PATH + instanceName;
int count = 1;
while (helixPropertyStore.exists(path, AccessOption.PERSISTENT)) {
// Thread.sleep() pauses the current thread but does not release any locks
Thread.sleep(clusterMapConfig.clustermapRetryDisablePartitionCompletionBackoffMs);
logger.info("{} th attempt on checking the completion of disabling partition.", ++count);
}
helixPropertyStore.stop();
}
use of org.apache.helix.zookeeper.datamodel.ZNRecord in project ambry by linkedin.
the class PropertyStoreToDataNodeConfigAdapter method get.
@Override
public DataNodeConfig get(String instanceName) {
String path = CONFIG_PATH + "/" + instanceName;
ZNRecord record = propertyStore.get(path, new Stat(), AccessOption.PERSISTENT);
return record != null ? converter.convert(record) : null;
}
use of org.apache.helix.zookeeper.datamodel.ZNRecord in project ambry by linkedin.
the class PropertyStoreToDataNodeConfigAdapter method set.
@Override
public boolean set(DataNodeConfig config) {
ZNRecord record = converter.convert(config);
String path = CONFIG_PATH + "/" + record.getId();
return propertyStore.set(path, record, AccessOption.PERSISTENT);
}
Aggregations