/**
* Copyright (C) 2012 LinkedIn Inc <opensource@linkedin.com>
*
* Licensed 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.
*/
package com.linkedin.helix.manager.file;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.linkedin.helix.ClusterMessagingService;
import com.linkedin.helix.ClusterView;
import com.linkedin.helix.ConfigAccessor;
import com.linkedin.helix.ConfigChangeListener;
import com.linkedin.helix.ControllerChangeListener;
import com.linkedin.helix.CurrentStateChangeListener;
import com.linkedin.helix.DataAccessor;
import com.linkedin.helix.ExternalViewChangeListener;
import com.linkedin.helix.HealthStateChangeListener;
import com.linkedin.helix.HelixAdmin;
import com.linkedin.helix.HelixDataAccessor;
import com.linkedin.helix.HelixManager;
import com.linkedin.helix.HelixProperty;
import com.linkedin.helix.IdealStateChangeListener;
import com.linkedin.helix.InstanceType;
import com.linkedin.helix.LiveInstanceChangeListener;
import com.linkedin.helix.MessageListener;
import com.linkedin.helix.NotificationContext;
import com.linkedin.helix.PreConnectCallback;
import com.linkedin.helix.PropertyType;
import com.linkedin.helix.ZNRecord;
import com.linkedin.helix.healthcheck.ParticipantHealthReportCollector;
import com.linkedin.helix.model.IdealState;
import com.linkedin.helix.model.InstanceConfig.InstanceConfigProperty;
import com.linkedin.helix.model.Message;
import com.linkedin.helix.participant.StateMachineEngine;
import com.linkedin.helix.participant.statemachine.StateModel;
import com.linkedin.helix.participant.statemachine.StateModelFactory;
import com.linkedin.helix.store.PropertyStore;
import com.linkedin.helix.store.zk.ZkHelixPropertyStore;
import com.linkedin.helix.tools.ClusterViewSerializer;
import com.linkedin.helix.tools.IdealStateCalculatorByShuffling;
@Deprecated
public class StaticFileHelixManager implements HelixManager
{
private static final Logger LOG = Logger.getLogger(StaticFileHelixManager.class.getName());
// for backward compatibility
// TODO remove it later
private final ClusterView _clusterView;
private final String _clusterName;
private final InstanceType _instanceType;
private final String _instanceName;
private boolean _isConnected;
public static final String _sessionId = "12345";
public static final String configFile = "configFile";
public StaticFileHelixManager(String clusterName, String instanceName, InstanceType instanceType,
String clusterViewFile)
{
_clusterName = clusterName;
_instanceName = instanceName;
_instanceType = instanceType;
_clusterView = ClusterViewSerializer.deserialize(new File(clusterViewFile));
}
// FIXIT
// reorder the messages to reduce the possibility that a S->M message for a
// given
// db partition gets executed before a O->S message
private static void addMessageInOrder(List<ZNRecord> msgList, Message newMsg)
{
String toState = newMsg.getToState();
if (toState.equals("MASTER"))
{
msgList.add(newMsg.getRecord());
}
if (toState.equals("SLAVE"))
{
msgList.add(0, newMsg.getRecord());
}
}
private static List<Message> computeMessagesForSimpleTransition(ZNRecord idealStateRecord)
{
List<Message> msgList = new ArrayList<Message>();
IdealState idealState = new IdealState(idealStateRecord);
for (String stateUnitKey : idealState.getPartitionSet())
{
Map<String, String> instanceStateMap;
instanceStateMap = idealState.getInstanceStateMap(stateUnitKey);
}
return msgList;
}
public static class DBParam
{
public String name;
public int partitions;
public DBParam(String n, int p)
{
name = n;
partitions = p;
}
}
public static ClusterView generateStaticConfigClusterView(String[] nodesInfo,
List<DBParam> dbParams, int replica)
{
// create mock cluster view
ClusterView view = new ClusterView();
// add nodes
List<ZNRecord> nodeConfigList = new ArrayList<ZNRecord>();
List<String> instanceNames = new ArrayList<String>();
Arrays.sort(nodesInfo, new Comparator<String>() {
@Override
public int compare(String str1, String str2)
{
return str1.compareTo(str2);
}
});
// set CONFIGS
for (String nodeInfo : nodesInfo)
{
int lastPos = nodeInfo.lastIndexOf(":");
if (lastPos == -1)
{
throw new IllegalArgumentException("nodeInfo should be in format of host:port, " + nodeInfo);
}
String host = nodeInfo.substring(0, lastPos);
String port = nodeInfo.substring(lastPos + 1);
String nodeId = host + "_" + port;
ZNRecord nodeConfig = new ZNRecord(nodeId);
nodeConfig.setSimpleField(InstanceConfigProperty.HELIX_ENABLED.toString(),
Boolean.toString(true));
nodeConfig.setSimpleField(InstanceConfigProperty.HELIX_HOST.toString(), host);
nodeConfig.setSimpleField(InstanceConfigProperty.HELIX_PORT.toString(), port);
instanceNames.add(nodeId);
nodeConfigList.add(nodeConfig);
}
view.setClusterPropertyList(PropertyType.CONFIGS, nodeConfigList);
// set IDEALSTATES
// compute ideal states for each db
List<ZNRecord> idealStates = new ArrayList<ZNRecord>();
for (DBParam dbParam : dbParams)
{
ZNRecord result = IdealStateCalculatorByShuffling.calculateIdealState(instanceNames,
dbParam.partitions, replica, dbParam.name);
idealStates.add(result);
}
view.setClusterPropertyList(PropertyType.IDEALSTATES, idealStates);
// calculate messages for transition using naive algorithm
Map<String, List<ZNRecord>> msgListForInstance = new HashMap<String, List<ZNRecord>>();
List<ZNRecord> idealStatesArray = view.getPropertyList(PropertyType.IDEALSTATES);
for (ZNRecord idealStateRecord : idealStatesArray)
{
// IdealState idealState = new IdealState(idealStateRecord);
List<Message> messages = computeMessagesForSimpleTransition(idealStateRecord);
for (Message message : messages)
{
// logger.info("Sending message to " + message.getTgtName() +
// " transition "
// + message.getStateUnitKey() + " from:" +
// message.getFromState() +
// " to:"
// + message.getToState());
// client.addMessage(message, message.getTgtName());
String instance = message.getTgtName();
List<ZNRecord> msgList = msgListForInstance.get(instance);
if (msgList == null)
{
msgList = new ArrayList<ZNRecord>();
msgListForInstance.put(instance, msgList);
}
// msgList.add(message);
addMessageInOrder(msgList, message);
}
}
// set INSTANCES
// put message lists into cluster view
List<ClusterView.MemberInstance> insList = new ArrayList<ClusterView.MemberInstance>();
for (Map.Entry<String, List<ZNRecord>> entry : msgListForInstance.entrySet())
{
String instance = entry.getKey();
List<ZNRecord> msgList = entry.getValue();
ClusterView.MemberInstance ins = view.getMemberInstance(instance, true);
ins.setInstanceProperty(PropertyType.MESSAGES, msgList);
// ins.setInstanceProperty(InstancePropertyType.CURRENTSTATES,
// null);
// ins.setInstanceProperty(InstancePropertyType.ERRORS, null);
// ins.setInstanceProperty(InstancePropertyType.STATUSUPDATES,
// null);
insList.add(ins);
}
// sort it
ClusterView.MemberInstance[] insArray = new ClusterView.MemberInstance[insList.size()];
insArray = insList.toArray(insArray);
Arrays.sort(insArray, new Comparator<ClusterView.MemberInstance>() {
@Override
public int compare(ClusterView.MemberInstance ins1, ClusterView.MemberInstance ins2)
{
return ins1.getInstanceName().compareTo(ins2.getInstanceName());
}
});
insList = Arrays.asList(insArray);
view.setInstances(insList);
return view;
}
@Override
public void disconnect()
{
_isConnected = false;
}
@Override
public void addIdealStateChangeListener(IdealStateChangeListener listener)
{
NotificationContext context = new NotificationContext(this);
context.setType(NotificationContext.Type.INIT);
List<ZNRecord> idealStates = _clusterView.getPropertyList(PropertyType.IDEALSTATES);
listener.onIdealStateChange(
HelixProperty.convertToTypedList(IdealState.class, idealStates), context);
}
@Override
public void addLiveInstanceChangeListener(LiveInstanceChangeListener listener)
{
throw new UnsupportedOperationException(
"addLiveInstanceChangeListener is not supported by File Based cluster manager");
}
@Override
public void addConfigChangeListener(ConfigChangeListener listener)
{
throw new UnsupportedOperationException(
"addConfigChangeListener() is NOT supported by File Based cluster manager");
}
@Override
public void addMessageListener(MessageListener listener, String instanceName)
{
NotificationContext context = new NotificationContext(this);
context.setType(NotificationContext.Type.INIT);
List<ZNRecord> messages;
messages = _clusterView.getMemberInstance(instanceName, true).getInstanceProperty(
PropertyType.MESSAGES);
listener.onMessage(instanceName, HelixProperty.convertToTypedList(Message.class, messages),
context);
}
@Override
public void addCurrentStateChangeListener(CurrentStateChangeListener listener,
String instanceName, String sessionId)
{
throw new UnsupportedOperationException(
"addCurrentStateChangeListener is not supported by File Based cluster manager");
}
@Override
public void addExternalViewChangeListener(ExternalViewChangeListener listener)
{
throw new UnsupportedOperationException(
"addExternalViewChangeListener() is NOT supported by File Based cluster manager");
}
@Override
public DataAccessor getDataAccessor()
{
return null;
}
@Override
public String getClusterName()
{
return _clusterName;
}
@Override
public String getInstanceName()
{
return _instanceName;
}
@Override
public void connect()
{
_isConnected = true;
}
@Override
public String getSessionId()
{
return _sessionId;
}
public static ClusterView convertStateModelMapToClusterView(String outFile, String instanceName,
StateModelFactory<StateModel> stateModelFactory)
{
Map<String, StateModel> currentStateMap = stateModelFactory.getStateModelMap();
ClusterView curView = new ClusterView();
ClusterView.MemberInstance memberInstance = curView.getMemberInstance(instanceName, true);
List<ZNRecord> curStateList = new ArrayList<ZNRecord>();
for (Map.Entry<String, StateModel> entry : currentStateMap.entrySet())
{
String stateUnitKey = entry.getKey();
String curState = entry.getValue().getCurrentState();
ZNRecord record = new ZNRecord(stateUnitKey);
record.setSimpleField(stateUnitKey, curState);
curStateList.add(record);
}
memberInstance.setInstanceProperty(PropertyType.CURRENTSTATES, curStateList);
// serialize to file
// String outFile = "/tmp/curClusterView_" + instanceName +".json";
if (outFile != null)
{
// ClusterViewSerializer serializer = new
// ClusterViewSerializer(outFile);
// serializer.serialize(curView);
ClusterViewSerializer.serialize(curView, new File(outFile));
}
return curView;
}
public static boolean verifyFileBasedClusterStates(String instanceName, String expectedFile,
String curFile)
{
boolean ret = true;
ClusterView expectedView = ClusterViewSerializer.deserialize(new File(expectedFile));
ClusterView curView = ClusterViewSerializer.deserialize(new File(curFile));
// ideal_state for instance with the given instanceName
Map<String, String> idealStates = new HashMap<String, String>();
for (ZNRecord idealStateItem : expectedView.getPropertyList(PropertyType.IDEALSTATES))
{
Map<String, Map<String, String>> allIdealStates = idealStateItem.getMapFields();
for (Map.Entry<String, Map<String, String>> entry : allIdealStates.entrySet())
{
if (entry.getValue().containsKey(instanceName))
{
String state = entry.getValue().get(instanceName);
idealStates.put(entry.getKey(), state);
}
}
}
ClusterView.MemberInstance memberInstance = curView.getMemberInstance(instanceName, false);
List<ZNRecord> curStateList = memberInstance.getInstanceProperty(PropertyType.CURRENTSTATES);
if (curStateList == null && idealStates.size() > 0)
{
LOG.info("current state is null");
return false;
} else if (curStateList == null && idealStates.size() == 0)
{
LOG.info("empty current state and ideal state");
return true;
} else if (curStateList.size() != idealStates.size())
{
LOG.info("Number of current states (" + curStateList.size() + ") mismatch "
+ "number of ideal states (" + idealStates.size() + ")");
return false;
}
for (ZNRecord record : curStateList)
{
String stateUnitKey = record.getId();
String curState = record.getSimpleField(stateUnitKey);
// if (!curState.equalsIgnoreCase("offline"))
// nonOfflineNr++;
if (!idealStates.containsKey(stateUnitKey))
{
LOG.error("Current state does not contain " + stateUnitKey);
ret = false;
continue;
}
String idealState = idealStates.get(stateUnitKey);
if (!curState.equalsIgnoreCase(idealState))
{
LOG.error("State mismatch--unit_key:" + stateUnitKey + " cur:" + curState + " ideal:"
+ idealState + " instance_name:" + instanceName);
ret = false;
continue;
}
}
return ret;
}
@Override
public boolean isConnected()
{
return _isConnected;
}
@Override
public long getLastNotificationTime()
{
return 0;
}
@Override
public void addControllerListener(ControllerChangeListener listener)
{
throw new UnsupportedOperationException(
"addControllerListener() is NOT supported by File Based cluster manager");
}
@Override
public boolean removeListener(Object listener)
{
// TODO Auto-generated method stub
return false;
}
@Override
public HelixAdmin getClusterManagmentTool()
{
// TODO Auto-generated method stub
return null;
}
@Override
public PropertyStore<ZNRecord> getPropertyStore()
{
// TODO Auto-generated method stub
return null;
}
@Override
public ClusterMessagingService getMessagingService()
{
// TODO Auto-generated method stub
return null;
}
@Override
public ParticipantHealthReportCollector getHealthReportCollector()
{
// TODO Auto-generated method stub
return null;
}
@Override
public InstanceType getInstanceType()
{
return _instanceType;
}
@Override
public void addHealthStateChangeListener(HealthStateChangeListener listener, String instanceName)
throws Exception
{
// TODO Auto-generated method stub
}
@Override
public String getVersion()
{
throw new UnsupportedOperationException("getVersion() not implemented in StaticFileClusterManager");
}
@Override
public StateMachineEngine getStateMachineEngine()
{
throw new UnsupportedOperationException("getStateMachineEngine() not implemented in StaticFileClusterManager");
}
@Override
public boolean isLeader()
{
throw new UnsupportedOperationException("isLeader() not implemented in StaticFileClusterManager");
}
@Override
public ConfigAccessor getConfigAccessor()
{
// TODO Auto-generated method stub
return null;
}
@Override
public void startTimerTasks()
{
// TODO Auto-generated method stub
}
@Override
public void stopTimerTasks()
{
// TODO Auto-generated method stub
}
@Override
public HelixDataAccessor getHelixDataAccessor()
{
// TODO Auto-generated method stub
return null;
}
@Override
public void addPreConnectCallback(PreConnectCallback callback)
{
// TODO Auto-generated method stub
}
@Override
public ZkHelixPropertyStore<ZNRecord> getHelixPropertyStore()
{
// TODO Auto-generated method stub
return null;
}
}