blob: c4c37fe386883101696089354a52e7f72025b849 [file] [log] [blame]
package org.apache.helix.integration;
/*
* 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 java.util.Arrays;
import java.util.Date;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.helix.BaseDataAccessor;
import org.apache.helix.HelixAdmin;
import org.apache.helix.HelixDataAccessor;
import org.apache.helix.NotificationContext;
import org.apache.helix.TestHelper;
import org.apache.helix.tools.ClusterVerifiers.BestPossibleExternalViewVerifier;
import org.apache.helix.tools.ClusterVerifiers.ZkHelixClusterVerifier;
import org.apache.helix.zookeeper.datamodel.ZNRecord;
import org.apache.helix.common.ZkTestBase;
import org.apache.helix.integration.manager.ClusterControllerManager;
import org.apache.helix.integration.manager.MockParticipantManager;
import org.apache.helix.manager.zk.ZKHelixAdmin;
import org.apache.helix.manager.zk.ZKHelixDataAccessor;
import org.apache.helix.manager.zk.ZkBaseDataAccessor;
import org.apache.helix.model.ClusterConstraints.ConstraintAttribute;
import org.apache.helix.model.ClusterConstraints.ConstraintType;
import org.apache.helix.model.IdealState;
import org.apache.helix.model.Message;
import org.apache.helix.model.StateModelDefinition;
import org.apache.helix.model.builder.ConstraintItemBuilder;
import org.apache.helix.participant.statemachine.StateModel;
import org.apache.helix.participant.statemachine.StateModelFactory;
import org.apache.helix.tools.ClusterStateVerifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.Test;
public class TestPartitionLevelTransitionConstraint extends ZkTestBase {
private static Logger LOG = LoggerFactory.getLogger(TestPartitionLevelTransitionConstraint.class);
final Queue<Message> _msgOrderList = new ConcurrentLinkedQueue<Message>();
public class BootstrapStateModel extends StateModel {
public void onBecomeBootstrapFromOffline(Message message, NotificationContext context) {
LOG.info("Become Bootstrap from Offline");
_msgOrderList.add(message);
}
public void onBecomeOfflineFromBootstrap(Message message, NotificationContext context) {
LOG.info("Become Offline from Bootstrap");
_msgOrderList.add(message);
}
public void onBecomeSlaveFromBootstrap(Message message, NotificationContext context) {
LOG.info("Become Slave from Bootstrap");
_msgOrderList.add(message);
}
public void onBecomeBootstrapFromSlave(Message message, NotificationContext context) {
LOG.info("Become Bootstrap from Slave");
_msgOrderList.add(message);
}
public void onBecomeMasterFromSlave(Message message, NotificationContext context) {
LOG.info("Become Master from Slave");
_msgOrderList.add(message);
}
public void onBecomeSlaveFromMaster(Message message, NotificationContext context) {
LOG.info("Become Slave from Master");
_msgOrderList.add(message);
}
}
public class BootstrapStateModelFactory extends StateModelFactory<BootstrapStateModel> {
@Override
public BootstrapStateModel createNewStateModel(String resource, String stateUnitKey) {
BootstrapStateModel model = new BootstrapStateModel();
return model;
}
}
@Test
public void test() throws Exception {
// Logger.getRootLogger().setLevel(Level.INFO);
String className = TestHelper.getTestClassName();
String methodName = TestHelper.getTestMethodName();
String clusterName = className + "_" + methodName;
int n = 2;
System.out.println("START " + clusterName + " at " + new Date(System.currentTimeMillis()));
TestHelper.setupCluster(clusterName, ZK_ADDR, 12918, // participant port
"localhost", // participant name prefix
"TestDB", // resource name prefix
1, // resources
1, // partitions per resource
n, // number of nodes
2, // replicas
"MasterSlave", false); // do not rebalance
// setup semi-auto ideal-state
BaseDataAccessor<ZNRecord> baseAccessor = new ZkBaseDataAccessor<ZNRecord>(_gZkClient);
HelixDataAccessor accessor = new ZKHelixDataAccessor(clusterName, baseAccessor);
StateModelDefinition stateModelDef = defineStateModel();
accessor.setProperty(accessor.keyBuilder().stateModelDef("Bootstrap"), stateModelDef);
IdealState idealState = accessor.getProperty(accessor.keyBuilder().idealStates("TestDB0"));
idealState.setStateModelDefRef("Bootstrap");
idealState.setReplicas("2");
idealState.getRecord().setListField("TestDB0_0",
Arrays.asList("localhost_12919", "localhost_12918"));
accessor.setProperty(accessor.keyBuilder().idealStates("TestDB0"), idealState);
// setup partition-level constraint
ConstraintItemBuilder constraintItemBuilder = new ConstraintItemBuilder();
constraintItemBuilder
.addConstraintAttribute(ConstraintAttribute.MESSAGE_TYPE.toString(), "STATE_TRANSITION")
.addConstraintAttribute(ConstraintAttribute.PARTITION.toString(), ".*")
.addConstraintAttribute(ConstraintAttribute.CONSTRAINT_VALUE.toString(), "1");
HelixAdmin admin = new ZKHelixAdmin(_gZkClient);
admin.setConstraint(clusterName, ConstraintType.MESSAGE_CONSTRAINT, "constraint1",
constraintItemBuilder.build());
ClusterControllerManager controller =
new ClusterControllerManager(ZK_ADDR, clusterName, "controller");
controller.syncStart();
// start 1st participant
MockParticipantManager[] participants = new MockParticipantManager[n];
String instanceName1 = "localhost_12918";
participants[0] = new MockParticipantManager(ZK_ADDR, clusterName, instanceName1);
participants[0].getStateMachineEngine().registerStateModelFactory("Bootstrap",
new BootstrapStateModelFactory());
participants[0].syncStart();
ZkHelixClusterVerifier verifier = new BestPossibleExternalViewVerifier.Builder(clusterName).setZkClient(_gZkClient)
.setWaitTillVerify(TestHelper.DEFAULT_REBALANCE_PROCESSING_WAIT_TIME)
.build();
boolean result = verifier.verify();
Assert.assertTrue(result);
// start 2nd participant which will be the master for Test0_0
String instanceName2 = "localhost_12919";
participants[1] = new MockParticipantManager(ZK_ADDR, clusterName, instanceName2);
participants[1].getStateMachineEngine().registerStateModelFactory("Bootstrap",
new BootstrapStateModelFactory());
participants[1].syncStart();
result = verifier.verify();
Assert.assertTrue(result);
// check we received the message in the right order
Assert.assertEquals(_msgOrderList.size(), 7, "_msgOrderList is:" + _msgOrderList.toString());
Message[] _msgOrderArray = _msgOrderList.toArray(new Message[0]);
assertMessage(_msgOrderArray[0], "OFFLINE", "BOOTSTRAP", instanceName1);
assertMessage(_msgOrderArray[1], "BOOTSTRAP", "SLAVE", instanceName1);
assertMessage(_msgOrderArray[2], "SLAVE", "MASTER", instanceName1);
// after we start the 2nd instance, the messages should be received in the following order:
// 1) offline->bootstrap for localhost_12919
// 2) bootstrap->slave for localhost_12919
// 3) master->slave for localhost_12918
// 4) slave->master for localhost_12919
assertMessage(_msgOrderArray[3], "OFFLINE", "BOOTSTRAP", instanceName2);
assertMessage(_msgOrderArray[4], "BOOTSTRAP", "SLAVE", instanceName2);
assertMessage(_msgOrderArray[5], "MASTER", "SLAVE", instanceName1);
assertMessage(_msgOrderArray[6], "SLAVE", "MASTER", instanceName2);
// clean up
// wait for all zk callbacks done
controller.syncStop();
for (int i = 0; i < n; i++) {
participants[i].syncStop();
}
deleteCluster(clusterName);
System.out.println("END " + clusterName + " at " + new Date(System.currentTimeMillis()));
}
private static void assertMessage(Message msg, String fromState, String toState, String instance) {
Assert.assertEquals(msg.getFromState(), fromState);
Assert.assertEquals(msg.getToState(), toState);
Assert.assertEquals(msg.getTgtName(), instance);
}
private static StateModelDefinition defineStateModel() {
StateModelDefinition.Builder builder = new StateModelDefinition.Builder("Bootstrap");
// Add states and their rank to indicate priority. Lower the rank higher the priority
builder.addState("MASTER", 1);
builder.addState("SLAVE", 2);
builder.addState("BOOTSTRAP", 3);
builder.addState("OFFLINE");
builder.addState("DROPPED");
// Set the initial state when the node starts
builder.initialState("OFFLINE");
// Add transitions between the states.
builder.addTransition("OFFLINE", "BOOTSTRAP", 3);
builder.addTransition("BOOTSTRAP", "SLAVE", 2);
builder.addTransition("SLAVE", "MASTER", 1);
builder.addTransition("MASTER", "SLAVE", 4);
builder.addTransition("SLAVE", "OFFLINE", 5);
builder.addTransition("OFFLINE", "DROPPED", 6);
// set constraints on states.
// static constraint
builder.upperBound("MASTER", 1);
// dynamic constraint, R means it should be derived based on the replication factor.
builder.dynamicUpperBound("SLAVE", "R");
StateModelDefinition statemodelDefinition = builder.build();
assert (statemodelDefinition.isValid());
return statemodelDefinition;
}
}