blob: f8955abbd86b09e9cc6cda741d71b65c2675c10c [file] [log] [blame]
package org.apache.helix.model;
/*
* 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import org.apache.helix.HelixDefinedState;
import org.apache.helix.zookeeper.datamodel.ZNRecord;
import org.apache.helix.model.StateModelDefinition.StateModelDefinitionProperty;
import org.apache.helix.tools.StateModelConfigGenerator;
import org.testng.Assert;
import org.testng.annotations.Test;
public class TestStateModelValidity {
/**
* Ensure that state models that we know to be good pass validation
*/
@Test
public void testValidModels() {
StateModelDefinition masterSlave =
new StateModelDefinition(StateModelConfigGenerator.generateConfigForMasterSlave());
Assert.assertTrue(masterSlave.isValid());
StateModelDefinition leaderStandby =
new StateModelDefinition(StateModelConfigGenerator.generateConfigForLeaderStandby());
Assert.assertTrue(leaderStandby.isValid());
StateModelDefinition onlineOffline =
new StateModelDefinition(StateModelConfigGenerator.generateConfigForOnlineOffline());
Assert.assertTrue(onlineOffline.isValid());
}
/**
* Ensure that Helix responds negatively if DROPPED is not specified
*/
@Test
public void testNoDroppedState() {
StateModelDefinition stateModel =
new StateModelDefinition.Builder("stateModel").initialState("OFFLINE").addState("OFFLINE")
.addState("MASTER").addState("SLAVE").addTransition("OFFLINE", "SLAVE")
.addTransition("SLAVE", "MASTER").addTransition("MASTER", "SLAVE")
.addTransition("SLAVE", "OFFLINE").build();
Assert.assertFalse(stateModel.isValid());
}
/**
* Ensure that Helix can catch when a state doesn't have a path to DROPPED
*/
@Test
public void testNoPathToDropped() {
StateModelDefinition stateModel =
new StateModelDefinition.Builder("stateModel").initialState("OFFLINE").addState("OFFLINE")
.addState("MASTER").addState("SLAVE").addState("DROPPED")
.addTransition("OFFLINE", "SLAVE").addTransition("SLAVE", "MASTER")
.addTransition("SLAVE", "OFFLINE").addTransition("OFFLINE", "DROPPED").build();
Assert.assertFalse(stateModel.isValid());
// now see that adding MASTER-DROPPED fixes the problem
stateModel =
new StateModelDefinition.Builder("stateModel").initialState("OFFLINE").addState("OFFLINE")
.addState("MASTER").addState("SLAVE").addState("DROPPED")
.addTransition("OFFLINE", "SLAVE").addTransition("SLAVE", "MASTER")
.addTransition("SLAVE", "OFFLINE").addTransition("OFFLINE", "DROPPED")
.addTransition("MASTER", "DROPPED").build();
Assert.assertTrue(stateModel.isValid());
}
/**
* The initial state should be added as a state, otherwise validation check should fail
*/
@Test
public void testInitialStateIsNotState() {
StateModelDefinition stateModel =
new StateModelDefinition.Builder("stateModel").initialState("OFFLINE").addState("MASTER")
.addState("SLAVE").addState("DROPPED").addTransition("OFFLINE", "SLAVE")
.addTransition("SLAVE", "MASTER").addTransition("SLAVE", "OFFLINE")
.addTransition("OFFLINE", "DROPPED").addTransition("MASTER", "SLAVE").build();
Assert.assertFalse(stateModel.isValid());
}
/**
* There should be an initial state, otherwise instantiation should fail
*/
@Test
public void testNoInitialState() {
try {
new StateModelDefinition.Builder("stateModel").addState("OFFLINE").addState("MASTER")
.addState("SLAVE").addState("DROPPED").addTransition("OFFLINE", "SLAVE")
.addTransition("SLAVE", "MASTER").addTransition("SLAVE", "OFFLINE")
.addTransition("OFFLINE", "DROPPED").addTransition("MASTER", "SLAVE").build();
Assert.fail("StateModelDefinition creation should fail if no initial state");
} catch (IllegalArgumentException e) {
}
}
/**
* SRC and DEST in a transition SRC-TEST must be valid states
*/
@Test
public void testTransitionsWithInvalidStates() {
// invalid to state
StateModelDefinition stateModel =
new StateModelDefinition.Builder("stateModel").initialState("OFFLINE").addState("OFFLINE")
.addState("MASTER").addState("SLAVE").addState("DROPPED")
.addTransition("OFFLINE", "SLAVE").addTransition("SLAVE", "MASTER")
.addTransition("SLAVE", "OFFLINE").addTransition("OFFLINE", "DROPPED")
.addTransition("MASTER", "SLAVE").addTransition("OFFLINE", "INVALID").build();
Assert.assertFalse(stateModel.isValid());
// invalid from state
stateModel =
new StateModelDefinition.Builder("stateModel").initialState("OFFLINE").addState("OFFLINE")
.addState("MASTER").addState("SLAVE").addState("DROPPED")
.addTransition("OFFLINE", "SLAVE").addTransition("SLAVE", "MASTER")
.addTransition("SLAVE", "OFFLINE").addTransition("OFFLINE", "DROPPED")
.addTransition("MASTER", "SLAVE").addTransition("INVALID", "MASTER").build();
Assert.assertFalse(stateModel.isValid());
}
/**
* The initial state should be able to reach all states, should fail validation otherwise
*/
@Test
public void testUnreachableState() {
StateModelDefinition stateModel =
new StateModelDefinition.Builder("stateModel").initialState("OFFLINE").addState("OFFLINE")
.addState("MASTER").addState("SLAVE").addState("DROPPED")
.addTransition("OFFLINE", "SLAVE").addTransition("SLAVE", "OFFLINE")
.addTransition("OFFLINE", "DROPPED").addTransition("MASTER", "SLAVE")
.addTransition("MASTER", "DROPPED").build();
Assert.assertFalse(stateModel.isValid());
}
/**
* The validator should fail on any detected infinite loops
*/
@Test
public void testLoopInStateModel() {
// create an infinite loop ONE --> TWO --> ONE
ZNRecord record = new ZNRecord("MasterSlave");
record.setSimpleField(StateModelDefinitionProperty.INITIAL_STATE.toString(), "OFFLINE");
List<String> statePriorityList =
Lists.newArrayList("ONE", "TWO", "THREE", "OFFLINE", "DROPPED", "ERROR");
record.setListField(StateModelDefinitionProperty.STATE_PRIORITY_LIST.toString(),
statePriorityList);
for (String state : statePriorityList) {
String key = state + ".meta";
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("count", "-1");
record.setMapField(key, metadata);
}
for (String state : statePriorityList) {
String key = state + ".next";
if (state.equals("ONE")) {
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("THREE", "TWO");
metadata.put("TWO", "TWO");
metadata.put("OFFLINE", "OFFLINE");
metadata.put("DROPPED", "DROPPED");
record.setMapField(key, metadata);
} else if (state.equals("TWO")) {
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("THREE", "ONE");
metadata.put("OFFLINE", "OFFLINE");
metadata.put("DROPPED", "OFFLINE");
record.setMapField(key, metadata);
} else if (state.equals("THREE")) {
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("OFFLINE", "OFFLINE");
metadata.put("DROPPED", "OFFLINE");
record.setMapField(key, metadata);
} else if (state.equals("OFFLINE")) {
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("ONE", "ONE");
metadata.put("TWO", "TWO");
metadata.put("THREE", "THREE");
metadata.put("DROPPED", "DROPPED");
record.setMapField(key, metadata);
} else if (state.equals("ERROR")) {
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("OFFLINE", "OFFLINE");
record.setMapField(key, metadata);
}
}
List<String> stateTransitionPriorityList = new ArrayList<String>();
record.setListField(StateModelDefinitionProperty.STATE_TRANSITION_PRIORITYLIST.toString(),
stateTransitionPriorityList);
StateModelDefinition stateModel = new StateModelDefinition(record);
Assert.assertFalse(stateModel.isValid());
}
/**
* This is the example used on the website, so this must work
*/
@Test
public void testBasic() {
StateModelDefinition stateModel = new StateModelDefinition.Builder("MasterSlave")
// OFFLINE is the state that the system starts in (initial state is REQUIRED)
.initialState("OFFLINE")
// Lowest number here indicates highest priority, no value indicates lowest priority
.addState("MASTER", 1).addState("SLAVE", 2).addState("OFFLINE")
// Note the special inclusion of the DROPPED state (REQUIRED)
.addState(HelixDefinedState.DROPPED.toString())
// No more than one master allowed
.upperBound("MASTER", 1)
// R indicates an upper bound of number of replicas for each partition
.dynamicUpperBound("SLAVE", "R")
// Add some high-priority transitions
.addTransition("SLAVE", "MASTER", 1).addTransition("OFFLINE", "SLAVE", 2)
// Using the same priority value indicates that these transitions can fire in any order
.addTransition("MASTER", "SLAVE", 3).addTransition("SLAVE", "OFFLINE", 3)
// Not specifying a value defaults to lowest priority
// Notice the inclusion of the OFFLINE to DROPPED transition
// Since every state has a path to OFFLINE, they each now have a path to DROPPED (REQUIRED)
.addTransition("OFFLINE", HelixDefinedState.DROPPED.toString())
// Create the StateModelDefinition instance
.build();
Assert.assertTrue(stateModel.isValid());
}
}