Skip to content

Commit d27a9e1

Browse files
Enable updating EV configs on IS updates
1 parent 2041a47 commit d27a9e1

File tree

2 files changed

+142
-1
lines changed

2 files changed

+142
-1
lines changed

helix-core/src/main/java/org/apache/helix/controller/GenericHelixController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ private static PipelineRegistry createDefaultRegistry(String pipelineName) {
554554
autoExitMaintenancePipeline.addStage(new MaintenanceRecoveryStage());
555555

556556
registry.register(ClusterEventType.IdealStateChange, dataRefresh, dataPreprocess,
557-
rebalancePipeline);
557+
rebalancePipeline, externalViewPipeline);
558558
registry.register(ClusterEventType.CurrentStateChange, dataRefresh, dataPreprocess,
559559
externalViewPipeline, rebalancePipeline);
560560
registry.register(ClusterEventType.InstanceConfigChange, dataRefresh, dataPreprocess,
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package org.apache.helix.controller.stages;
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
import java.util.Map;
23+
24+
import java.util.Objects;
25+
import org.apache.helix.PropertyKey.Builder;
26+
import org.apache.helix.controller.dataproviders.ResourceControllerDataProvider;
27+
import org.apache.helix.model.ExternalView;
28+
import org.apache.helix.model.IdealState;
29+
import org.testng.Assert;
30+
import org.testng.annotations.Test;
31+
32+
/**
33+
* Test to verify that ExternalViewComputeStage on IdealStateChange events only copies
34+
* simple fields and not partition assignments from IdealState to ExternalView.
35+
*/
36+
public class TestExternalViewComputeOnIdealStateChange extends BaseStageTest {
37+
38+
@Test
39+
public void testExternalViewComputeOnlySimpleFieldsFromIdealState() throws InterruptedException {
40+
String resourceName = "TestDB";
41+
String partition1 = resourceName + "_0";
42+
String partition2 = resourceName + "_1";
43+
String instance1 = "localhost_1001";
44+
String instance2 = "localhost_1002";
45+
String instance3 = "localhost_1003";
46+
47+
setupStateModel();
48+
49+
// create initial IdealState
50+
IdealState idealState = new IdealState(resourceName);
51+
idealState.setStateModelDefRef("MasterSlave");
52+
idealState.setRebalanceMode(IdealState.RebalanceMode.SEMI_AUTO);
53+
idealState.setNumPartitions(2);
54+
idealState.setReplicas("2");
55+
idealState.setMinActiveReplicas(1);
56+
57+
// Set initial simple field config
58+
idealState.getRecord().setSimpleField("CUSTOM_CONFIG_KEY", "initial_value");
59+
idealState.getRecord().setSimpleField("CUSTOM_TIMEOUT", "5000");
60+
61+
// Set initial partition assignments in IdealState
62+
idealState.setPartitionState(partition1, instance1, "MASTER");
63+
idealState.setPartitionState(partition1, instance2, "SLAVE");
64+
idealState.setPartitionState(partition2, instance2, "MASTER");
65+
idealState.setPartitionState(partition2, instance3, "SLAVE");
66+
67+
setSingleIdealState(idealState);
68+
69+
// run pipeline stages to setup the environment
70+
ClusterEvent event = new ClusterEvent(_clusterName, ClusterEventType.IdealStateChange);
71+
ResourceControllerDataProvider cache = new ResourceControllerDataProvider(_clusterName);
72+
event.addAttribute(AttributeName.ControllerDataProvider.name(), cache);
73+
event.addAttribute(AttributeName.helixmanager.name(), manager);
74+
75+
runStage(event, new ReadClusterDataStage());
76+
runStage(event, new ResourceComputationStage());
77+
runStage(event, new CurrentStateComputationStage());
78+
runStage(event, new ExternalViewComputeStage());
79+
80+
Builder keyBuilder = accessor.keyBuilder();
81+
82+
// update IdealState
83+
IdealState updatedIdealState = new IdealState(resourceName);
84+
updatedIdealState.setStateModelDefRef("MasterSlave");
85+
updatedIdealState.setRebalanceMode(IdealState.RebalanceMode.SEMI_AUTO);
86+
updatedIdealState.setNumPartitions(2);
87+
updatedIdealState.setReplicas("2");
88+
updatedIdealState.setMinActiveReplicas(1);
89+
90+
// Update simple field config, this should get copied to ExternalView
91+
updatedIdealState.getRecord().setSimpleField("CUSTOM_CONFIG_KEY", "updated_value");
92+
updatedIdealState.getRecord().setSimpleField("CUSTOM_TIMEOUT", "10000");
93+
updatedIdealState.getRecord().setSimpleField("NEW_CONFIG", "new_config_value");
94+
95+
// Update partition assignments, this shouldn't get copied to ExternalView
96+
updatedIdealState.setPartitionState(partition1, instance3, "MASTER");
97+
updatedIdealState.setPartitionState(partition1, instance1, "SLAVE");
98+
updatedIdealState.setPartitionState(partition2, instance1, "MASTER");
99+
updatedIdealState.setPartitionState(partition2, instance2, "SLAVE");
100+
101+
setSingleIdealState(updatedIdealState);
102+
103+
cache = new ResourceControllerDataProvider(_clusterName);
104+
event.addAttribute(AttributeName.ControllerDataProvider.name(), cache);
105+
106+
// Re-run all stages with new cache to ensure it has both updated IdealState and current state
107+
runStage(event, new ReadClusterDataStage());
108+
runStage(event, new ResourceComputationStage());
109+
runStage(event, new CurrentStateComputationStage());
110+
runStage(event, new ExternalViewComputeStage());
111+
112+
// verify results
113+
ExternalView updatedEV = accessor.getProperty(keyBuilder.externalView(resourceName));
114+
System.out.println("Updated ExternalView simple fields: " + updatedEV.getRecord().getSimpleFields());
115+
Assert.assertNotNull(updatedEV, "ExternalView should exist after running ExternalViewComputeStage");
116+
117+
Assert.assertEquals(updatedEV.getRecord().getSimpleField("CUSTOM_CONFIG_KEY"), "updated_value",
118+
"Simple field CUSTOM_CONFIG_KEY should be copied from IdealState");
119+
Assert.assertEquals(updatedEV.getRecord().getSimpleField("CUSTOM_TIMEOUT"), "10000",
120+
"Simple field CUSTOM_TIMEOUT should be copied from IdealState");
121+
Assert.assertEquals(updatedEV.getRecord().getSimpleField("NEW_CONFIG"), "new_config_value",
122+
"New simple field NEW_CONFIG should be copied from IdealState");
123+
124+
Map<String, String> finalPartition1States = updatedEV.getStateMap(partition1);
125+
Map<String, String> finalPartition2States = updatedEV.getStateMap(partition2);
126+
127+
Map<String, String> idealStatePartition1 = updatedIdealState.getInstanceStateMap(partition1);
128+
Map<String, String> idealStatePartition2 = updatedIdealState.getInstanceStateMap(partition2);
129+
130+
// verify partition assignments are not equal to IdealState assignments
131+
Assert.assertFalse(
132+
Objects.equals(finalPartition1States, idealStatePartition1),
133+
"Final EV partition1 assignments should NOT match updated IdealState assignments"
134+
);
135+
Assert.assertFalse(
136+
Objects.equals(finalPartition2States, idealStatePartition2),
137+
"Final EV partition2 assignments should NOT match updated IdealState assignments"
138+
);
139+
140+
}
141+
}

0 commit comments

Comments
 (0)