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


The following commit(s) were added to refs/heads/master by this push:
     new ce33735dd [apache/helix] -- Fix PreferenceList Ordering Changes during 
Maintenance Mode (#2778)
ce33735dd is described below

commit ce33735dda1dd8614cb8d84094119de1a4b30484
Author: Himanshu Kandwal <[email protected]>
AuthorDate: Tue Mar 19 15:49:55 2024 -0700

    [apache/helix] -- Fix PreferenceList Ordering Changes during Maintenance 
Mode (#2778)
    
    Fixed PreferenceList Ordering Changes (indeterminism) during Maintenance 
Mode.
---
 .../rebalancer/MaintenanceRebalancer.java          | 31 ++++++-
 .../rebalancer/TestMaintenanceRebalancer.java      | 60 +++++++++++++
 ...MaintenanceRebalancer.ComputeNewIdealState.json | 98 ++++++++++++++++++++++
 3 files changed, 187 insertions(+), 2 deletions(-)

diff --git 
a/helix-core/src/main/java/org/apache/helix/controller/rebalancer/MaintenanceRebalancer.java
 
b/helix-core/src/main/java/org/apache/helix/controller/rebalancer/MaintenanceRebalancer.java
index 9f04de535..9b5b4f929 100644
--- 
a/helix-core/src/main/java/org/apache/helix/controller/rebalancer/MaintenanceRebalancer.java
+++ 
b/helix-core/src/main/java/org/apache/helix/controller/rebalancer/MaintenanceRebalancer.java
@@ -28,6 +28,7 @@ import 
org.apache.helix.controller.dataproviders.ResourceControllerDataProvider;
 import org.apache.helix.controller.stages.CurrentStateOutput;
 import org.apache.helix.model.IdealState;
 import org.apache.helix.model.Partition;
+import org.apache.helix.model.StateModelDefinition;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -55,12 +56,38 @@ public class MaintenanceRebalancer extends 
SemiAutoRebalancer<ResourceController
 
     // One principal is to prohibit DROP -> OFFLINE and OFFLINE -> DROP state 
transitions.
     // Derived preference list from current state with state priority
+    StateModelDefinition stateModelDef = 
clusterData.getStateModelDef(currentIdealState.getStateModelDefRef());
+
     for (Partition partition : currentStateMap.keySet()) {
       Map<String, String> stateMap = currentStateMap.get(partition);
       List<String> preferenceList = new ArrayList<>(stateMap.keySet());
+
+      /**
+       * This sorting preserves the ordering of current state hosts in the 
order of current IS pref list
+       * Example:
+       * ideal state pref-list: [A, B, C]
+       * current-state: {
+       *     A: FOLLOWER,
+       *     B: LEADER,
+       *     C: FOLLOWER
+       * }
+       * Lets say newPrefList = new ArrayList<>(current-state.keySet()) => [C, 
B, A]
+       *
+       * Sort 1: Sort based on preference-list order:
+       * --------------------------------------------------------
+       * newPrefList = [C, B, A] => [A, B, C]
+       */
       Collections.sort(preferenceList, new 
PreferenceListNodeComparator(stateMap,
-          
clusterData.getStateModelDef(currentIdealState.getStateModelDefRef()),
-          Collections.<String>emptyList()));
+          stateModelDef, 
currentIdealState.getPreferenceList(partition.getPartitionName())));
+
+      /**
+       * Sort 2: Sort based on state-priority order:
+       * --------------------------------------------------------
+       * newPrefList = [A, B, C] => [B, A, C]
+       * Here, A will be 2nd and C will be third always as both have same 
priority so original (pref-list) order will be maintained.
+       */
+      preferenceList.sort(new StatePriorityComparator(stateMap, 
stateModelDef));
+
       currentIdealState.setPreferenceList(partition.getPartitionName(), 
preferenceList);
     }
     LOG.info(String
diff --git 
a/helix-core/src/test/java/org/apache/helix/controller/rebalancer/TestMaintenanceRebalancer.java
 
b/helix-core/src/test/java/org/apache/helix/controller/rebalancer/TestMaintenanceRebalancer.java
new file mode 100644
index 000000000..50cdd418f
--- /dev/null
+++ 
b/helix-core/src/test/java/org/apache/helix/controller/rebalancer/TestMaintenanceRebalancer.java
@@ -0,0 +1,60 @@
+package org.apache.helix.controller.rebalancer;
+
+import java.util.List;
+import java.util.Map;
+
+import 
org.apache.helix.controller.dataproviders.ResourceControllerDataProvider;
+import org.apache.helix.controller.stages.CurrentStateOutput;
+import org.apache.helix.model.IdealState;
+import org.apache.helix.model.MasterSlaveSMD;
+import org.apache.helix.model.Partition;
+import org.apache.helix.util.TestInputLoader;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TestMaintenanceRebalancer {
+
+  private static final String RESOURCE_NAME = "testResource";
+  private static final String PARTITION_NAME = "testResourcePartition";
+
+  @Test(dataProvider = "TestComputeIdealStateInput")
+  public void testComputeIdealState(String comment, String stateModelName, 
List<String> liveInstances,
+      List<String> preferenceList, Map<String, String> currentStateMap, 
List<String> expectedPrefList) {
+    System.out.println("Test case comment: " + comment);
+    MaintenanceRebalancer rebalancer = new MaintenanceRebalancer();
+
+    Partition partition = new Partition(PARTITION_NAME);
+    CurrentStateOutput currentStateOutput = new CurrentStateOutput();
+    for (String instance : currentStateMap.keySet()) {
+      currentStateOutput.setCurrentState(RESOURCE_NAME, partition, instance, 
currentStateMap.get(instance));
+    }
+
+    IdealState currentIdealState = new IdealState(RESOURCE_NAME);
+    currentIdealState.setRebalanceMode(IdealState.RebalanceMode.FULL_AUTO);
+    
currentIdealState.setRebalancerClassName("org.apache.helix.controller.rebalancer.waged.WagedRebalancer");
+    currentIdealState.setStateModelDefRef(stateModelName);
+    currentIdealState.setPreferenceList(PARTITION_NAME, preferenceList);
+
+    ResourceControllerDataProvider dataCache = 
mock(ResourceControllerDataProvider.class);
+    
when(dataCache.getStateModelDef("MasterSlave")).thenReturn(MasterSlaveSMD.build());
+
+    IdealState updatedIdealState = rebalancer
+        .computeNewIdealState(RESOURCE_NAME, currentIdealState, 
currentStateOutput, dataCache);
+
+    List<String> partitionPrefList = 
updatedIdealState.getPreferenceList(PARTITION_NAME);
+    Assert.assertTrue(partitionPrefList.equals(expectedPrefList));
+  }
+
+  @DataProvider(name = "TestComputeIdealStateInput")
+  public Object[][] loadTestComputeIdealStateInput() {
+    final String[] params = {
+        "comment", "stateModel", "liveInstances", "preferenceList", 
"currentStateMap", "expectedPreferenceList"
+    };
+    return 
TestInputLoader.loadTestInputs("MaintenanceRebalancer.ComputeNewIdealState.json",
 params);
+  }
+
+}
diff --git 
a/helix-core/src/test/resources/MaintenanceRebalancer.ComputeNewIdealState.json 
b/helix-core/src/test/resources/MaintenanceRebalancer.ComputeNewIdealState.json
new file mode 100644
index 000000000..9212b90bd
--- /dev/null
+++ 
b/helix-core/src/test/resources/MaintenanceRebalancer.ComputeNewIdealState.json
@@ -0,0 +1,98 @@
+[
+  {
+    "comment": "[Case 1] Same state - No (A, B, C) -> (A, C, B) for different 
hash order",
+    "stateModel": "MasterSlave",
+    "liveInstances": [],
+    "preferenceList": [
+      "lKmBBEPisM", "8PNpMU7EgT", "HOs3rOFCad"
+    ],
+    "currentStateMap": {
+      "lKmBBEPisM" : "MASTER",
+      "8PNpMU7EgT" : "SLAVE",
+      "HOs3rOFCad" : "SLAVE"
+    },
+    "expectedPreferenceList": [
+      "lKmBBEPisM", "8PNpMU7EgT", "HOs3rOFCad"
+    ]
+  },
+  {
+    "comment": "[Case 2] Same state - No (A, B, C) -> (A, C, B) for different 
hash order",
+    "stateModel": "MasterSlave",
+    "liveInstances": [],
+    "preferenceList": [
+      "Q3ZZuCeBpu", "QY9U91XBYo", "GMYBW20gmC"
+    ],
+    "currentStateMap": {
+      "Q3ZZuCeBpu" : "MASTER",
+      "QY9U91XBYo" : "SLAVE",
+      "GMYBW20gmC" : "SLAVE"
+    },
+    "expectedPreferenceList": [
+      "Q3ZZuCeBpu", "QY9U91XBYo", "GMYBW20gmC"
+    ]
+  },
+  {
+    "comment": "[Case 3] second becomes leader, (A, B, C) -> (B, A, C) for 
different hash order",
+    "stateModel": "MasterSlave",
+    "liveInstances": [],
+    "preferenceList": [
+      "lKmBBEPisM", "8PNpMU7EgT", "HOs3rOFCad"
+    ],
+    "currentStateMap": {
+      "lKmBBEPisM" : "SLAVE",
+      "8PNpMU7EgT" : "MASTER",
+      "HOs3rOFCad" : "SLAVE"
+    },
+    "expectedPreferenceList": [
+      "8PNpMU7EgT", "lKmBBEPisM", "HOs3rOFCad"
+    ]
+  },
+  {
+    "comment": "[Case 4] second becomes leader, (A, B, C) -> (B, A, C) for 
different hash order",
+    "stateModel": "MasterSlave",
+    "liveInstances": [],
+    "preferenceList": [
+      "Q3ZZuCeBpu", "QY9U91XBYo", "GMYBW20gmC"
+    ],
+    "currentStateMap": {
+      "Q3ZZuCeBpu" : "SLAVE",
+      "QY9U91XBYo" : "MASTER",
+      "GMYBW20gmC" : "SLAVE"
+    },
+    "expectedPreferenceList": [
+      "QY9U91XBYo", "Q3ZZuCeBpu", "GMYBW20gmC"
+    ]
+  },
+  {
+    "comment": "[Case 5] leader becomes offline, (A, B, C) -> (B, C, A) for 
different hash order",
+    "stateModel": "MasterSlave",
+    "liveInstances": [],
+    "preferenceList": [
+      "lKmBBEPisM", "8PNpMU7EgT", "HOs3rOFCad"
+    ],
+    "currentStateMap": {
+      "lKmBBEPisM" : "OFFLINE",
+      "8PNpMU7EgT" : "MASTER",
+      "HOs3rOFCad" : "SLAVE"
+    },
+    "expectedPreferenceList": [
+      "8PNpMU7EgT", "HOs3rOFCad", "lKmBBEPisM"
+    ]
+  },
+  {
+    "comment": "[Case 6] leader becomes offline, (A, B, C) -> (B, C, A) for 
different hash order",
+    "stateModel": "MasterSlave",
+    "liveInstances": [],
+    "preferenceList": [
+      "Q3ZZuCeBpu", "QY9U91XBYo", "GMYBW20gmC"
+    ],
+    "currentStateMap": {
+      "Q3ZZuCeBpu" : "OFFLINE",
+      "QY9U91XBYo" : "MASTER",
+      "GMYBW20gmC" : "SLAVE"
+    },
+    "expectedPreferenceList": [
+      "QY9U91XBYo", "GMYBW20gmC", "Q3ZZuCeBpu"
+    ]
+  }
+]

Reply via email to