This is an automated email from the ASF dual-hosted git repository. jxue pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/helix.git
commit 90a3832a0f9811dac0007031ca33fbe4e0bd15ec Author: Qi (Quincy) Qu <[email protected]> AuthorDate: Tue Feb 8 16:53:40 2022 -0500 Implement java API and utils for virtual topology group (#1935) Add comment to VirtualTopologyGroupService. --- .../constants/VirtualTopologyGroupConstants.java | 1 + .../rebalancer/waged/model/AssignableNode.java | 1 - .../org/apache/helix/model/InstanceConfig.java | 21 ++- .../service/VirtualTopologyGroupService.java | 197 +++++++++++++++++++++ .../service/TestVirtualTopologyGroupService.java | 192 ++++++++++++++++++++ 5 files changed, 407 insertions(+), 5 deletions(-) diff --git a/helix-core/src/main/java/org/apache/helix/cloud/constants/VirtualTopologyGroupConstants.java b/helix-core/src/main/java/org/apache/helix/cloud/constants/VirtualTopologyGroupConstants.java index a92e195..d97173a 100644 --- a/helix-core/src/main/java/org/apache/helix/cloud/constants/VirtualTopologyGroupConstants.java +++ b/helix-core/src/main/java/org/apache/helix/cloud/constants/VirtualTopologyGroupConstants.java @@ -23,6 +23,7 @@ package org.apache.helix.cloud.constants; public class VirtualTopologyGroupConstants { public static final String GROUP_NAME = "virtualTopologyGroupName"; public static final String GROUP_NUMBER = "virtualTopologyGroupNumber"; + public static final String AUTO_MAINTENANCE_MODE_DISABLED = "autoMaintenanceModeDisabled"; public static final String GROUP_NAME_SPLITTER = "_"; public static final String PATH_NAME_SPLITTER = "/"; public static final String VIRTUAL_FAULT_ZONE_TYPE = "virtualZone"; diff --git a/helix-core/src/main/java/org/apache/helix/controller/rebalancer/waged/model/AssignableNode.java b/helix-core/src/main/java/org/apache/helix/controller/rebalancer/waged/model/AssignableNode.java index aae2328..e29052a 100644 --- a/helix-core/src/main/java/org/apache/helix/controller/rebalancer/waged/model/AssignableNode.java +++ b/helix-core/src/main/java/org/apache/helix/controller/rebalancer/waged/model/AssignableNode.java @@ -19,7 +19,6 @@ package org.apache.helix.controller.rebalancer.waged.model; * under the License. */ -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; 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 143b610..3ab3ea0 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 @@ -63,6 +63,8 @@ public class InstanceConfig extends HelixProperty { public static final int WEIGHT_NOT_SET = -1; public static final int MAX_CONCURRENT_TASK_NOT_SET = -1; private static final int TARGET_TASK_THREAD_POOL_SIZE_NOT_SET = -1; + private static final String DOMAIN_FIELD_SPLITTER = ","; + private static final String DOMAIN_VALUE_JOINER = "="; private static final Logger _logger = LoggerFactory.getLogger(InstanceConfig.class.getName()); @@ -156,10 +158,9 @@ public class InstanceConfig extends HelixProperty { if (domain == null || domain.isEmpty()) { return domainAsMap; } - - String[] pathPairs = domain.trim().split(","); + String[] pathPairs = domain.trim().split(DOMAIN_FIELD_SPLITTER); for (String pair : pathPairs) { - String[] values = pair.split("="); + String[] values = pair.split(DOMAIN_VALUE_JOINER); if (values.length != 2 || values[0].isEmpty() || values[1].isEmpty()) { throw new IllegalArgumentException( String.format("Domain-Value pair %s is not valid.", pair)); @@ -173,12 +174,24 @@ public class InstanceConfig extends HelixProperty { /** * Domain represents a hierarchy identifier for an instance. * Example: "cluster=myCluster,zone=myZone1,rack=myRack,host=hostname,instance=instance001". - * @return */ public void setDomain(String domain) { _record.setSimpleField(InstanceConfigProperty.DOMAIN.name(), domain); } + /** + * Set domain from its map representation. + * @param domainMap domain as a map + */ + public void setDomain(Map<String, String> domainMap) { + String domain = domainMap + .entrySet() + .stream() + .map(entry -> entry.getKey() + DOMAIN_VALUE_JOINER + entry.getValue()) + .collect(Collectors.joining(DOMAIN_FIELD_SPLITTER)); + setDomain(domain); + } + public int getWeight() { String w = _record.getSimpleField(InstanceConfigProperty.INSTANCE_WEIGHT.name()); if (w != null) { diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/service/VirtualTopologyGroupService.java b/helix-rest/src/main/java/org/apache/helix/rest/server/service/VirtualTopologyGroupService.java new file mode 100644 index 0000000..997b880 --- /dev/null +++ b/helix-rest/src/main/java/org/apache/helix/rest/server/service/VirtualTopologyGroupService.java @@ -0,0 +1,197 @@ +package org.apache.helix.rest.server.service; + +/* + * 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 com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.apache.helix.AccessOption; +import org.apache.helix.ConfigAccessor; +import org.apache.helix.HelixAdmin; +import org.apache.helix.HelixDataAccessor; +import org.apache.helix.HelixException; +import org.apache.helix.PropertyPathBuilder; +import org.apache.helix.cloud.constants.VirtualTopologyGroupConstants; +import org.apache.helix.cloud.topology.FifoVirtualGroupAssignmentAlgorithm; +import org.apache.helix.cloud.topology.VirtualGroupAssignmentAlgorithm; +import org.apache.helix.model.CloudConfig; +import org.apache.helix.model.ClusterConfig; +import org.apache.helix.model.ClusterTopologyConfig; +import org.apache.helix.model.InstanceConfig; +import org.apache.helix.rest.server.json.cluster.ClusterTopology; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.helix.zookeeper.zkclient.DataUpdater; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Service for virtual topology group. + * It's a virtualization layer on top of physical fault domain and topology in cloud environments. + * The service computes the mapping from virtual group to instances based on the current cluster topology and update the + * information to cluster and all instances in the cluster. + */ +public class VirtualTopologyGroupService { + private static final Logger LOG = LoggerFactory.getLogger(VirtualTopologyGroupService.class); + + private final HelixAdmin _helixAdmin; + private final ClusterService _clusterService; + private final ConfigAccessor _configAccessor; + private final HelixDataAccessor _dataAccessor; + private final VirtualGroupAssignmentAlgorithm _assignmentAlgorithm; + + public VirtualTopologyGroupService(HelixAdmin helixAdmin, ClusterService clusterService, + ConfigAccessor configAccessor, HelixDataAccessor dataAccessor) { + _helixAdmin = helixAdmin; + _clusterService = clusterService; + _configAccessor = configAccessor; + _dataAccessor = dataAccessor; + _assignmentAlgorithm = FifoVirtualGroupAssignmentAlgorithm.getInstance(); + } + + /** + * Add virtual topology group for a cluster. + * This includes calculating the virtual group assignment for all instances in the cluster then update instance config + * and cluster config. We override {@link ClusterConfig.ClusterConfigProperty#TOPOLOGY} and + * {@link ClusterConfig.ClusterConfigProperty#FAULT_ZONE_TYPE} for cluster config, and add new field to + * {@link InstanceConfig.InstanceConfigProperty#DOMAIN} that contains virtual topology group information. + * This is only supported for cloud environments. Cluster is expected to be in maintenance mode during config change. + * @param clusterName the cluster name. + * @param customFields custom fields, {@link VirtualTopologyGroupConstants#GROUP_NAME} + * and {@link VirtualTopologyGroupConstants#GROUP_NUMBER} are required, + * {@link VirtualTopologyGroupConstants#AUTO_MAINTENANCE_MODE_DISABLED} is optional. + * -- if set ture, the cluster will NOT automatically enter/exit maintenance mode during this API call; + * -- if set false or not set, the cluster will automatically enter maintenance mode and exit after + * the call succeeds. It won't proceed if the cluster is already in maintenance mode. + * Either case, the cluster must be in maintenance mode before config change. + */ + public void addVirtualTopologyGroup(String clusterName, Map<String, String> customFields) { + // validation + CloudConfig cloudConfig = _configAccessor.getCloudConfig(clusterName); + if (cloudConfig == null || !cloudConfig.isCloudEnabled()) { + throw new HelixException( + "Cloud is not enabled, addVirtualTopologyGroup is not allowed to run in non-cloud environment."); + } + ClusterConfig clusterConfig = _configAccessor.getClusterConfig(clusterName); + Preconditions.checkState(clusterConfig.isTopologyAwareEnabled(), + "Topology-aware rebalance is not enabled in cluster " + clusterName); + String groupName = customFields.get(VirtualTopologyGroupConstants.GROUP_NAME); + String groupNumberStr = customFields.get(VirtualTopologyGroupConstants.GROUP_NUMBER); + Preconditions.checkArgument(!StringUtils.isEmpty(groupName), "virtualTopologyGroupName cannot be empty!"); + Preconditions.checkArgument(!StringUtils.isEmpty(groupNumberStr), "virtualTopologyGroupNumber cannot be empty!"); + int numGroups = 0; + try { + numGroups = Integer.parseInt(groupNumberStr); + Preconditions.checkArgument(numGroups > 0, "Number of virtual groups should be positive."); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("virtualTopologyGroupNumber " + groupNumberStr + " is not an integer.", ex); + } + LOG.info("Computing virtual topology group for cluster {} with param {}", clusterName, customFields); + + // compute group assignment + ClusterTopology clusterTopology = _clusterService.getClusterTopology(clusterName); + Preconditions.checkArgument(numGroups <= clusterTopology.getAllInstances().size(), + "Number of virtual groups cannot be greater than the number of instances."); + Map<String, Set<String>> assignment = + _assignmentAlgorithm.computeAssignment(numGroups, groupName, clusterTopology.toZoneMapping()); + + boolean autoMaintenanceModeDisabled = Boolean.parseBoolean( + customFields.getOrDefault(VirtualTopologyGroupConstants.AUTO_MAINTENANCE_MODE_DISABLED, "false")); + // if auto mode is NOT disabled, let service enter maintenance mode and exit after the API succeeds. + if (!autoMaintenanceModeDisabled) { + Preconditions.checkState(!_helixAdmin.isInMaintenanceMode(clusterName), + "This operation is not allowed if cluster is already in maintenance mode before the API call. " + + "Please set autoMaintenanceModeDisabled=true if this is intended."); + _helixAdmin.manuallyEnableMaintenanceMode(clusterName, true, + "Enable maintenanceMode for virtual topology group change.", customFields); + } + Preconditions.checkState(_helixAdmin.isInMaintenanceMode(clusterName), + "Cluster is not in maintenance mode. This is required for virtual topology group setting. " + + "Please set autoMaintenanceModeDisabled=false (default) to let the cluster enter maintenance mode automatically, " + + "or use autoMaintenanceModeDisabled=true and control cluster maintenance mode in client side."); + + updateConfigs(clusterName, clusterConfig, assignment); + if (!autoMaintenanceModeDisabled) { + _helixAdmin.manuallyEnableMaintenanceMode(clusterName, false, + "Disable maintenanceMode after virtual topology group change.", customFields); + } + } + + private void updateConfigs(String clusterName, ClusterConfig clusterConfig, Map<String, Set<String>> assignment) { + List<String> zkPaths = new ArrayList<>(); + List<DataUpdater<ZNRecord>> updaters = new ArrayList<>(); + createInstanceConfigUpdater(clusterName, assignment).forEach((zkPath, updater) -> { + zkPaths.add(zkPath); + updaters.add(updater); + }); + // update instance config + boolean[] results = _dataAccessor.updateChildren(zkPaths, updaters, AccessOption.EPHEMERAL); + for (int i = 0; i < results.length; i++) { + if (!results[i]) { + throw new HelixException("Failed to update instance config for path " + zkPaths.get(i)); + } + } + // update cluster config + String virtualTopologyString = computeVirtualTopologyString(clusterConfig); + clusterConfig.setTopology(virtualTopologyString); + clusterConfig.setFaultZoneType(VirtualTopologyGroupConstants.VIRTUAL_FAULT_ZONE_TYPE); + _configAccessor.updateClusterConfig(clusterName, clusterConfig); + LOG.info("Successfully update instance and cluster config for {}", clusterName); + } + + @VisibleForTesting + static String computeVirtualTopologyString(ClusterConfig clusterConfig) { + ClusterTopologyConfig clusterTopologyConfig = ClusterTopologyConfig.createFromClusterConfig(clusterConfig); + String endNodeType = clusterTopologyConfig.getEndNodeType(); + String[] splits = new String[] {"", VirtualTopologyGroupConstants.VIRTUAL_FAULT_ZONE_TYPE, endNodeType}; + return String.join(VirtualTopologyGroupConstants.PATH_NAME_SPLITTER, splits); + } + + /** + * Create updater for instance config for async update. + * @param clusterName cluster name of the instances. + * @param assignment virtual group assignment. + * @return a map from instance zkPath to its {@link DataUpdater} to update. + */ + @VisibleForTesting + static Map<String, DataUpdater<ZNRecord>> createInstanceConfigUpdater( + String clusterName, Map<String, Set<String>> assignment) { + Map<String, DataUpdater<ZNRecord>> updaters = new HashMap<>(); + for (Map.Entry<String, Set<String>> entry : assignment.entrySet()) { + String virtualGroup = entry.getKey(); + for (String instanceName : entry.getValue()) { + String path = PropertyPathBuilder.instanceConfig(clusterName, instanceName); + updaters.put(path, currentData -> { + InstanceConfig instanceConfig = new InstanceConfig(currentData); + Map<String, String> domainMap = instanceConfig.getDomainAsMap(); + domainMap.put(VirtualTopologyGroupConstants.VIRTUAL_FAULT_ZONE_TYPE, virtualGroup); + instanceConfig.setDomain(domainMap); + return instanceConfig.getRecord(); + }); + } + } + return updaters; + } +} diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/service/TestVirtualTopologyGroupService.java b/helix-rest/src/test/java/org/apache/helix/rest/server/service/TestVirtualTopologyGroupService.java new file mode 100644 index 0000000..ab1c53f --- /dev/null +++ b/helix-rest/src/test/java/org/apache/helix/rest/server/service/TestVirtualTopologyGroupService.java @@ -0,0 +1,192 @@ +package org.apache.helix.rest.server.service; + +/* + * 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 com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.helix.ConfigAccessor; +import org.apache.helix.HelixAdmin; +import org.apache.helix.HelixDataAccessor; +import org.apache.helix.HelixException; +import org.apache.helix.cloud.azure.AzureConstants; +import org.apache.helix.cloud.constants.CloudProvider; +import org.apache.helix.model.CloudConfig; +import org.apache.helix.model.ClusterConfig; +import org.apache.helix.model.HelixConfigScope; +import org.apache.helix.model.InstanceConfig; +import org.apache.helix.model.builder.HelixConfigScopeBuilder; +import org.apache.helix.rest.server.json.cluster.ClusterTopology; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.helix.zookeeper.zkclient.DataUpdater; +import org.testng.Assert; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.apache.helix.cloud.constants.VirtualTopologyGroupConstants.*; +import static org.mockito.Mockito.*; + + +public class TestVirtualTopologyGroupService { + private static final String TEST_CLUSTER = "Test_Cluster"; + private static final String TEST_CLUSTER0 = "TestCluster_0"; + private static final String TEST_CLUSTER1 = "TestCluster_1"; + + private final ConfigAccessor _configAccessor = mock(ConfigAccessor.class); + private final HelixDataAccessor _dataAccessor = mock(HelixDataAccessor.class); + private InstanceConfig _instanceConfig0; + private InstanceConfig _instanceConfig1; + private InstanceConfig _instanceConfig2; + private Map<String, DataUpdater<ZNRecord>> _updaterMap; + private HelixAdmin _helixAdmin; + private VirtualTopologyGroupService _service; + + @BeforeTest + public void prepare() { + Map<String, Set<String>> assignment = new HashMap<>(); + _instanceConfig0 = new InstanceConfig("instance_0"); + _instanceConfig0.setDomain("helixZoneId=zone0"); + _instanceConfig1 = new InstanceConfig("instance_1"); + _instanceConfig1.setDomain("helixZoneId=zone0"); + _instanceConfig2 = new InstanceConfig("instance_2"); + _instanceConfig2.setDomain("helixZoneId=zone1"); + + assignment.put("virtual_group_0", ImmutableSet.of("instance_0", "instance_1")); + assignment.put("virtual_group_1", ImmutableSet.of("instance_2")); + _updaterMap = VirtualTopologyGroupService.createInstanceConfigUpdater(TEST_CLUSTER, assignment); + + ClusterConfig clusterConfig = new ClusterConfig(TEST_CLUSTER0); + clusterConfig.setFaultZoneType(AzureConstants.AZURE_FAULT_ZONE_TYPE); + clusterConfig.setTopology(AzureConstants.AZURE_TOPOLOGY); + clusterConfig.setTopologyAwareEnabled(true); + when(_configAccessor.getClusterConfig(TEST_CLUSTER0)).thenReturn(clusterConfig); + + CloudConfig.Builder cloudConfigBuilder = new CloudConfig.Builder(); + cloudConfigBuilder.setCloudEnabled(true); + cloudConfigBuilder.setCloudProvider(CloudProvider.AZURE); + cloudConfigBuilder.setCloudID("TestID"); + CloudConfig cloudConfig = cloudConfigBuilder.build(); + when(_configAccessor.getCloudConfig(TEST_CLUSTER0)).thenReturn(cloudConfig); + + _helixAdmin = mock(HelixAdmin.class); + when(_helixAdmin.isInMaintenanceMode(anyString())).thenReturn(true); + + boolean[] results = new boolean[2]; + results[0] = results[1] = true; + when(_dataAccessor.updateChildren(anyList(), anyList(), anyInt())).thenReturn(results); + ClusterService clusterService = mock(ClusterService.class); + when(clusterService.getClusterTopology(anyString())).thenReturn(prepareClusterTopology()); + _service = new VirtualTopologyGroupService(_helixAdmin, clusterService, _configAccessor, _dataAccessor); + } + + @Test(expectedExceptions = HelixException.class, expectedExceptionsMessageRegExp = "Cloud is not enabled.*") + public void testClusterCloudConfigSetup() { + ClusterConfig clusterConfig1 = new ClusterConfig(TEST_CLUSTER1); + when(_configAccessor.getClusterConfig(TEST_CLUSTER1)).thenReturn(clusterConfig1); + _service.addVirtualTopologyGroup(TEST_CLUSTER1, ImmutableMap.of(GROUP_NAME, "test-group", GROUP_NUMBER, "2")); + } + + @Test + public void testVirtualTopologyGroupService() { + _service.addVirtualTopologyGroup(TEST_CLUSTER0, ImmutableMap.of( + GROUP_NAME, "test-group", GROUP_NUMBER, "2", AUTO_MAINTENANCE_MODE_DISABLED, "true")); + verify(_dataAccessor, times(1)).updateChildren(anyList(), anyList(), anyInt()); + verify(_configAccessor, times(1)).updateClusterConfig(anyString(), any()); + } + + @Test(expectedExceptions = IllegalStateException.class, + expectedExceptionsMessageRegExp = "This operation is not allowed if cluster is already in maintenance mode.*") + public void testMaintenanceModeCheckBeforeApiCall() { + _service.addVirtualTopologyGroup(TEST_CLUSTER0, ImmutableMap.of(GROUP_NAME, "test-group", GROUP_NUMBER, "2")); + } + + @Test(expectedExceptions = IllegalStateException.class, + expectedExceptionsMessageRegExp = "Cluster is not in maintenance mode. This is required for virtual topology group setting. " + + "Please set autoMaintenanceModeDisabled=false.*") + public void testMaintenanceModeCheckAfter() { + try { + when(_helixAdmin.isInMaintenanceMode(anyString())).thenReturn(false); + _service.addVirtualTopologyGroup(TEST_CLUSTER0, ImmutableMap.of(GROUP_NAME, "test-group", GROUP_NUMBER, "2")); + } finally { + when(_helixAdmin.isInMaintenanceMode(anyString())).thenReturn(true); + } + } + + @Test(expectedExceptions = IllegalArgumentException.class, + expectedExceptionsMessageRegExp = "Number of virtual groups cannot be greater than the number of instances.*") + public void testNumberOfInstanceCheck() { + _service.addVirtualTopologyGroup(TEST_CLUSTER0, ImmutableMap.of( + GROUP_NAME, "test-group", GROUP_NUMBER, "10", AUTO_MAINTENANCE_MODE_DISABLED, "true")); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testParamValidation() { + _service.addVirtualTopologyGroup(TEST_CLUSTER0, ImmutableMap.of(GROUP_NUMBER, "2")); + } + + @Test(dataProvider = "instanceTestProvider") + public void testInstanceConfigUpdater(String zkPath, InstanceConfig instanceConfig, Map<String, String> expectedDomain) { + ZNRecord update = _updaterMap.get(zkPath).update(instanceConfig.getRecord()); + InstanceConfig updatedConfig = new InstanceConfig(update); + Assert.assertEquals(updatedConfig.getDomainAsMap(), expectedDomain); + } + + @DataProvider + public Object[][] instanceTestProvider() { + return new Object[][] { + {computeZkPath("instance_0"), _instanceConfig0, + ImmutableMap.of("helixZoneId", "zone0", VIRTUAL_FAULT_ZONE_TYPE, "virtual_group_0")}, + {computeZkPath("instance_1"), _instanceConfig1, + ImmutableMap.of("helixZoneId", "zone0", VIRTUAL_FAULT_ZONE_TYPE, "virtual_group_0")}, + {computeZkPath("instance_2"), _instanceConfig2, + ImmutableMap.of("helixZoneId", "zone1", VIRTUAL_FAULT_ZONE_TYPE, "virtual_group_1")} + }; + } + + @Test + public void testVirtualTopologyString() { + ClusterConfig testConfig = new ClusterConfig("testId"); + testConfig.setTopologyAwareEnabled(true); + testConfig.setTopology("/zone/instance"); + Assert.assertEquals(VirtualTopologyGroupService.computeVirtualTopologyString(testConfig), + "/virtualZone/instance"); + } + + private static ClusterTopology prepareClusterTopology() { + List<ClusterTopology.Zone> zones = ImmutableList.of( + new ClusterTopology.Zone("zone0", ImmutableList.of( + new ClusterTopology.Instance("instance_0"), new ClusterTopology.Instance("instance_1"))), + new ClusterTopology.Zone("zone1", ImmutableList.of(new ClusterTopology.Instance("instance_2")))); + return new ClusterTopology(TEST_CLUSTER0, zones, ImmutableSet.of("instance_0", "instance_1", "instance_2")); + } + + private static String computeZkPath(String instanceName) { + HelixConfigScope scope = new HelixConfigScopeBuilder(HelixConfigScope.ConfigScopeProperty.PARTICIPANT) + .forCluster(TEST_CLUSTER) + .forParticipant(instanceName) + .build(); + return scope.getZkPath(); + } +}
