package org.apache.solr.common.cloud;
/*
* 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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.noggit.JSONWriter;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.HashPartitioner.Range;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Immutable state of the cloud. Normally you can get the state by using
* {@link ZkStateReader#getClusterState()}.
*/
public class ClusterState implements JSONWriter.Writable {
private static Logger log = LoggerFactory.getLogger(ClusterState.class);
private Integer zkClusterStateVersion;
private final Map<String, Map<String,Slice>> collectionStates; // Map<collectionName, Map<sliceName,Slice>>
private final Set<String> liveNodes;
private final HashPartitioner hp = new HashPartitioner();
private final Map<String,RangeInfo> rangeInfos = new HashMap<String,RangeInfo>();
private final Map<String,Map<String,ZkNodeProps>> leaders = new HashMap<String,Map<String,ZkNodeProps>>();
/**
* Use this constr when ClusterState is meant for publication.
*
* hashCode and equals will only depend on liveNodes and not clusterStateVersion.
*/
public ClusterState(Set<String> liveNodes,
Map<String, Map<String,Slice>> collectionStates) {
this(null, liveNodes, collectionStates);
}
/**
* Use this constr when ClusterState is meant for consumption.
*/
public ClusterState(Integer zkClusterStateVersion, Set<String> liveNodes,
Map<String, Map<String,Slice>> collectionStates) {
this.liveNodes = new HashSet<String>(liveNodes.size());
this.liveNodes.addAll(liveNodes);
this.collectionStates = new HashMap<String, Map<String,Slice>>(collectionStates.size());
this.collectionStates.putAll(collectionStates);
addRangeInfos(collectionStates.keySet());
getShardLeaders();
}
private void getShardLeaders() {
Set<Entry<String,Map<String,Slice>>> collections = collectionStates.entrySet();
for (Entry<String,Map<String,Slice>> collection : collections) {
Map<String,Slice> state = collection.getValue();
Set<Entry<String,Slice>> slices = state.entrySet();
for (Entry<String,Slice> sliceEntry : slices) {
Slice slice = sliceEntry.getValue();
Map<String,Replica> shards = slice.getReplicasMap();
Set<Entry<String,Replica>> shardsEntries = shards.entrySet();
for (Entry<String,Replica> shardEntry : shardsEntries) {
ZkNodeProps props = shardEntry.getValue();
if (props.containsKey(ZkStateReader.LEADER_PROP)) {
Map<String,ZkNodeProps> leadersForCollection = leaders.get(collection.getKey());
if (leadersForCollection == null) {
leadersForCollection = new HashMap<String,ZkNodeProps>();
leaders.put(collection.getKey(), leadersForCollection);
}
leadersForCollection.put(sliceEntry.getKey(), props);
break; // we found the leader for this shard
}
}
}
}
}
/**
* Get properties of a shard leader for specific collection.
*/
public ZkNodeProps getLeader(String collection, String shard) {
Map<String,ZkNodeProps> collectionLeaders = leaders.get(collection);
if (collectionLeaders == null) return null;
return collectionLeaders.get(shard);
}
/**
* Get shard properties or null if shard is not found.
*/
public Replica getShardProps(final String collection, final String coreNodeName) {
Map<String, Slice> slices = getSlices(collection);
for(Slice slice: slices.values()) {
if(slice.getReplicasMap().get(coreNodeName)!=null) {
return slice.getReplicasMap().get(coreNodeName);
}
}
return null;
}
private void addRangeInfos(Set<String> collections) {
for (String collection : collections) {
addRangeInfo(collection);
}
}
/**
* Get the index Slice for collection.
*/
public Slice getSlice(String collection, String slice) {
if (collectionStates.containsKey(collection)
&& collectionStates.get(collection).containsKey(slice))
return collectionStates.get(collection).get(slice);
return null;
}
/**
* Get all slices for collection.
*/
public Map<String, Slice> getSlices(String collection) {
if(!collectionStates.containsKey(collection))
return null;
return Collections.unmodifiableMap(collectionStates.get(collection));
}
/**
* Get collection names.
*/
public Set<String> getCollections() {
return Collections.unmodifiableSet(collectionStates.keySet());
}
/**
* @return Map<collectionName, Map<sliceName,Slice>>
*/
public Map<String, Map<String, Slice>> getCollectionStates() {
return Collections.unmodifiableMap(collectionStates);
}
/**
* Get names of the currently live nodes.
*/
public Set<String> getLiveNodes() {
return Collections.unmodifiableSet(liveNodes);
}
/**
* Get shardId for core.
* @param coreNodeName in the form of nodeName_coreName
*/
public String getShardId(String coreNodeName) {
for (Entry<String, Map<String, Slice>> states: collectionStates.entrySet()){
for(Entry<String, Slice> slices: states.getValue().entrySet()) {
for(Entry<String, Replica> shards: slices.getValue().getReplicasMap().entrySet()){
if(coreNodeName.equals(shards.getKey())) {
return slices.getKey();
}
}
}
}
return null;
}
/**
* Check if node is alive.
*/
public boolean liveNodesContain(String name) {
return liveNodes.contains(name);
}
public RangeInfo getRanges(String collection) {
// TODO: store this in zk
RangeInfo rangeInfo = rangeInfos.get(collection);
return rangeInfo;
}
private RangeInfo addRangeInfo(String collection) {
List<Range> ranges;
RangeInfo rangeInfo;
rangeInfo = new RangeInfo();
Map<String,Slice> slices = getSlices(collection);
if (slices == null) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Can not find collection "
+ collection + " in " + this);
}
Set<String> shards = slices.keySet();
ArrayList<String> shardList = new ArrayList<String>(shards.size());
shardList.addAll(shards);
Collections.sort(shardList);
ranges = hp.partitionRange(shards.size(), Integer.MIN_VALUE, Integer.MAX_VALUE);
rangeInfo.ranges = ranges;
rangeInfo.shardList = shardList;
rangeInfos.put(collection, rangeInfo);
return rangeInfo;
}
/**
* Get shard id for hash. This is used when determining which Slice the
* document is to be submitted to.
*/
public String getShard(int hash, String collection) {
RangeInfo rangInfo = getRanges(collection);
int cnt = 0;
for (Range range : rangInfo.ranges) {
if (range.includes(hash)) {
return rangInfo.shardList.get(cnt);
}
cnt++;
}
throw new IllegalStateException("The HashPartitioner failed");
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("live nodes:" + liveNodes);
sb.append(" collections:" + collectionStates);
return sb.toString();
}
/**
* Create ClusterState by reading the current state from zookeeper.
*/
public static ClusterState load(SolrZkClient zkClient, Set<String> liveNodes) throws KeeperException, InterruptedException {
Stat stat = new Stat();
byte[] state = zkClient.getData(ZkStateReader.CLUSTER_STATE,
null, stat, true);
return load(stat.getVersion(), state, liveNodes);
}
/**
* Create ClusterState from json string that is typically stored in zookeeper.
*
* Use {@link ClusterState#load(SolrZkClient, Set)} instead, unless you want to
* do something more when getting the data - such as get the stat, set watch, etc.
*
* @param version zk version of the clusterstate.json file (bytes)
* @param bytes clusterstate.json as a byte array
* @param liveNodes list of live nodes
* @return the ClusterState
*/
public static ClusterState load(Integer version, byte[] bytes, Set<String> liveNodes) {
if (bytes == null || bytes.length == 0) {
return new ClusterState(version, liveNodes, Collections.<String, Map<String,Slice>>emptyMap());
}
// System.out.println("########## Loading ClusterState:" + new String(bytes));
LinkedHashMap<String, Object> stateMap = (LinkedHashMap<String, Object>) ZkStateReader.fromJSON(bytes);
HashMap<String,Map<String, Slice>> state = new HashMap<String,Map<String,Slice>>();
for(String collectionName: stateMap.keySet()){
Map<String, Object> collection = (Map<String, Object>)stateMap.get(collectionName);
Map<String, Slice> slices = new LinkedHashMap<String,Slice>();
for (Entry<String,Object> sliceEntry : collection.entrySet()) {
Slice slice = new Slice(sliceEntry.getKey(), null, (Map<String,Object>)sliceEntry.getValue());
slices.put(slice.getName(), slice);
}
state.put(collectionName, slices);
}
return new ClusterState(version, liveNodes, state);
}
@Override
public void write(JSONWriter jsonWriter) {
jsonWriter.write(collectionStates);
}
private class RangeInfo {
private List<Range> ranges;
private ArrayList<String> shardList;
}
/**
* The version of clusterstate.json in ZooKeeper.
*
* @return null if ClusterState was created for publication, not consumption
*/
public Integer getZkClusterStateVersion() {
return zkClusterStateVersion;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((zkClusterStateVersion == null) ? 0 : zkClusterStateVersion.hashCode());
result = prime * result + ((liveNodes == null) ? 0 : liveNodes.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
ClusterState other = (ClusterState) obj;
if (zkClusterStateVersion == null) {
if (other.zkClusterStateVersion != null) return false;
} else if (!zkClusterStateVersion.equals(other.zkClusterStateVersion)) return false;
if (liveNodes == null) {
if (other.liveNodes != null) return false;
} else if (!liveNodes.equals(other.liveNodes)) return false;
return true;
}
}