blob: 67adc9667ddf4dd71c7f36f3cf0ae86adb62167b [file] [log] [blame]
/*
* 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.
*/
package org.apache.solr.common.cloud;
import static org.apache.solr.common.util.Utils.STANDARDOBJBUILDER;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.DocCollection.CollectionStateProps;
import org.apache.solr.common.cloud.Replica.ReplicaStateProps;
import org.apache.solr.common.util.Utils;
import org.noggit.JSONParser;
import org.noggit.JSONWriter;
import org.noggit.ObjectBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Immutable state of the cloud. Normally you can get the state by using {@code
* ZkStateReader#getClusterState()}.
*
* @lucene.experimental
*/
public class ClusterState implements JSONWriter.Writable {
/** Cluster Prop that is http or https. */
public static final String URL_SCHEME = "urlScheme";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final Map<String, CollectionRef> collectionStates, immutableCollectionStates;
private Set<String> liveNodes;
private Set<String> hostAllowList;
/** Use this constr when ClusterState is meant for consumption. */
public ClusterState(Set<String> liveNodes, Map<String, DocCollection> collectionStates) {
this(getRefMap(collectionStates), liveNodes);
}
private static Map<String, CollectionRef> getRefMap(Map<String, DocCollection> collectionStates) {
Map<String, CollectionRef> collRefs = new LinkedHashMap<>(collectionStates.size());
for (Entry<String, DocCollection> entry : collectionStates.entrySet()) {
final DocCollection c = entry.getValue();
collRefs.put(entry.getKey(), new CollectionRef(c));
}
return collRefs;
}
/**
* Use this if all the collection states are not readily available and some needs to be lazily
* loaded (parameter order different from constructor above to have different erasures)
*/
public ClusterState(Map<String, CollectionRef> collectionStates, Set<String> liveNodes) {
this.liveNodes = new HashSet<>(liveNodes.size());
this.liveNodes.addAll(liveNodes);
this.collectionStates = new LinkedHashMap<>(collectionStates);
this.immutableCollectionStates = Collections.unmodifiableMap(this.collectionStates);
}
/**
* Returns a new cluster state object modified with the given collection.
*
* @param collectionName the name of the modified (or deleted) collection
* @param collection the collection object. A null value deletes the collection from the state
* @return the updated cluster state which preserves the current live nodes
*/
public ClusterState copyWith(String collectionName, DocCollection collection) {
LinkedHashMap<String, CollectionRef> collections = new LinkedHashMap<>(collectionStates);
if (collection == null) {
collections.remove(collectionName);
} else {
collections.put(collectionName, new CollectionRef(collection));
}
return new ClusterState(collections, liveNodes);
}
/**
* Returns true if the specified collection name exists, false otherwise.
*
* <p>Implementation note: This method resolves the collection reference by calling {@link
* CollectionRef#get()} which can make a call to ZooKeeper. This is necessary because the
* semantics of how collection list is loaded have changed in SOLR-6629.
*/
public boolean hasCollection(String collectionName) {
return getCollectionOrNull(collectionName) != null;
}
/** Get the named DocCollection object, or throw an exception if it doesn't exist. */
public DocCollection getCollection(String collection) {
DocCollection coll = getCollectionOrNull(collection);
if (coll == null)
throw new SolrException(ErrorCode.BAD_REQUEST, "Could not find collection : " + collection);
return coll;
}
public CollectionRef getCollectionRef(String coll) {
return collectionStates.get(coll);
}
/**
* Returns the corresponding {@link DocCollection} object for the given collection name if such a
* collection exists. Returns null otherwise. Equivalent to getCollectionOrNull(collectionName,
* false)
*/
public DocCollection getCollectionOrNull(String collectionName) {
return getCollectionOrNull(collectionName, false);
}
/**
* Returns the corresponding {@link DocCollection} object for the given collection name if such a
* collection exists. Returns null otherwise.
*
* @param collectionName Name of the collection
* @param allowCached allow LazyCollectionRefs to use a time-based cached value
* <p>Implementation note: This method resolves the collection reference by calling {@link
* CollectionRef#get()} which may make a call to ZooKeeper. This is necessary because the
* semantics of how collection list is loaded have changed in SOLR-6629.
*/
public DocCollection getCollectionOrNull(String collectionName, boolean allowCached) {
CollectionRef ref = collectionStates.get(collectionName);
return ref == null ? null : ref.get(allowCached);
}
/**
* Get a map of collection name vs DocCollection objects
*
* <p>Implementation note: This method resolves the collection reference by calling {@link
* CollectionRef#get()} which can make a call to ZooKeeper. This is necessary because the
* semantics of how collection list is loaded have changed in SOLR-6629.
*
* @return a map of collection name vs DocCollection object
*/
public Map<String, DocCollection> getCollectionsMap() {
Map<String, DocCollection> result = new HashMap<>(collectionStates.size());
for (Entry<String, CollectionRef> entry : collectionStates.entrySet()) {
DocCollection collection = entry.getValue().get();
if (collection != null) {
result.put(entry.getKey(), collection);
}
}
return result;
}
/** Get names of the currently live nodes. */
public Set<String> getLiveNodes() {
return Collections.unmodifiableSet(liveNodes);
}
public String getShardId(String nodeName, String coreName) {
return getShardId(null, nodeName, coreName);
}
public String getShardId(String collectionName, String nodeName, String coreName) {
Collection<CollectionRef> states = collectionStates.values();
if (collectionName != null) {
CollectionRef c = collectionStates.get(collectionName);
if (c != null) states = Collections.singletonList(c);
}
for (CollectionRef ref : states) {
DocCollection coll = ref.get();
if (coll == null) continue; // this collection go tremoved in between, skip
for (Slice slice : coll.getSlices()) {
for (Replica replica : slice.getReplicas()) {
// TODO: for really large clusters, we could 'index' on this
String rnodeName = replica.getStr(ReplicaStateProps.NODE_NAME);
String rcore = replica.getStr(ReplicaStateProps.CORE_NAME);
if (nodeName.equals(rnodeName) && coreName.equals(rcore)) {
return slice.getName();
}
}
}
}
return null;
}
/** Check if node is alive. */
public boolean liveNodesContain(String name) {
return liveNodes.contains(name);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("live nodes:").append(liveNodes);
sb.append("\n");
sb.append("collections:").append(collectionStates);
return sb.toString();
}
/**
* Create a ClusterState from Json. This method doesn't support legacy configName location and
* thus don't call it where that's important
*
* @param bytes a byte array of a Json representation of a mapping from collection name to the
* Json representation of a {@link DocCollection} as written by {@link #write(JSONWriter)}. It
* can represent one or more collections.
* @param liveNodes list of live nodes
* @return the ClusterState
*/
public static ClusterState createFromJson(
int version, byte[] bytes, Set<String> liveNodes, DocCollection.PrsSupplier prsSupplier) {
if (bytes == null || bytes.length == 0) {
return new ClusterState(liveNodes, Collections.<String, DocCollection>emptyMap());
}
@SuppressWarnings({"unchecked"})
Map<String, Object> stateMap =
(Map<String, Object>) Utils.fromJSON(bytes, 0, bytes.length, STR_INTERNER_OBJ_BUILDER);
return createFromCollectionMap(version, stateMap, liveNodes, prsSupplier);
}
@Deprecated
public static ClusterState createFromJson(int version, byte[] bytes, Set<String> liveNodes) {
return createFromJson(version, bytes, liveNodes, null);
}
public static ClusterState createFromCollectionMap(
int version,
Map<String, Object> stateMap,
Set<String> liveNodes,
DocCollection.PrsSupplier prsSupplier) {
Map<String, CollectionRef> collections = new LinkedHashMap<>(stateMap.size());
for (Entry<String, Object> entry : stateMap.entrySet()) {
String collectionName = entry.getKey();
@SuppressWarnings({"unchecked"})
DocCollection coll =
collectionFromObjects(
collectionName, (Map<String, Object>) entry.getValue(), version, prsSupplier);
collections.put(collectionName, new CollectionRef(coll));
}
return new ClusterState(collections, liveNodes);
}
@Deprecated
public static ClusterState createFromCollectionMap(
int version, Map<String, Object> stateMap, Set<String> liveNodes) {
return createFromCollectionMap(version, stateMap, liveNodes, null);
}
// TODO move to static DocCollection.loadFromMap
public static DocCollection collectionFromObjects(
String name, Map<String, Object> objs, int version, DocCollection.PrsSupplier prsSupplier) {
Map<String, Object> props;
Map<String, Slice> slices;
if (Boolean.parseBoolean(String.valueOf(objs.get(CollectionStateProps.PER_REPLICA_STATE)))) {
if (log.isDebugEnabled()) {
log.debug("a collection {} has per-replica state", name);
}
}
@SuppressWarnings({"unchecked"})
Map<String, Object> sliceObjs = (Map<String, Object>) objs.get(CollectionStateProps.SHARDS);
if (sliceObjs == null) {
// legacy format from 4.0... there was no separate "shards" level to contain the collection
// shards.
slices = Slice.loadAllFromMap(name, objs);
props = Collections.emptyMap();
} else {
slices = Slice.loadAllFromMap(name, sliceObjs);
props = new HashMap<>(objs);
objs.remove(CollectionStateProps.SHARDS);
}
Object routerObj = props.get(CollectionStateProps.DOC_ROUTER);
DocRouter router;
if (routerObj == null) {
router = DocRouter.DEFAULT;
} else if (routerObj instanceof String) {
// back compat with Solr4.4
router = DocRouter.getDocRouter((String) routerObj);
} else {
@SuppressWarnings({"rawtypes"})
Map routerProps = (Map) routerObj;
router = DocRouter.getDocRouter((String) routerProps.get("name"));
}
return new DocCollection(name, slices, props, router, version, prsSupplier);
}
@Override
public void write(JSONWriter jsonWriter) {
LinkedHashMap<String, DocCollection> map = new LinkedHashMap<>();
for (Entry<String, CollectionRef> e : collectionStates.entrySet()) {
if (e.getValue().getClass() == CollectionRef.class) {
DocCollection coll = e.getValue().get();
map.put(coll.getName(), coll);
}
}
jsonWriter.write(map);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
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 (liveNodes == null) {
return other.liveNodes == null;
} else return liveNodes.equals(other.liveNodes);
}
/** Internal API used only by ZkStateReader */
void setLiveNodes(Set<String> liveNodes) {
this.liveNodes = liveNodes;
}
/**
* Be aware that this may return collections which may not exist now. You can confirm that this
* collection exists after verifying CollectionRef.get() != null
*/
public Map<String, CollectionRef> getCollectionStates() {
return immutableCollectionStates;
}
/**
* Gets the set of allowed hosts (host:port) built from the set of live nodes. The set is cached
* to be reused.
*/
public Set<String> getHostAllowList() {
if (hostAllowList == null) {
hostAllowList =
getLiveNodes().stream()
.map((liveNode) -> liveNode.substring(0, liveNode.indexOf('_')))
.collect(Collectors.toSet());
}
return hostAllowList;
}
/**
* Iterate over collections. Unlike {@link #getCollectionStates()} collections passed to the
* consumer are guaranteed to exist.
*
* @param consumer collection consumer.
*/
public void forEachCollection(Consumer<DocCollection> consumer) {
collectionStates.forEach(
(s, collectionRef) -> {
try {
DocCollection collection = collectionRef.get();
if (collection != null) {
consumer.accept(collection);
}
} catch (SolrException e) {
if (e.getCause() != null
&& e.getCause().getClass().getName().endsWith("NoNodeException")) {
// don't do anything. This collection does not exist
} else {
throw e;
}
}
});
}
public static class CollectionRef {
protected final AtomicInteger gets = new AtomicInteger();
private final DocCollection coll;
public int getCount() {
return gets.get();
}
public CollectionRef(DocCollection coll) {
this.coll = coll;
}
/**
* Return the DocCollection, always refetching if lazy. Equivalent to get(false)
*
* @return The collection state modeled in zookeeper
*/
public DocCollection get() {
return get(false);
}
/**
* Return the DocCollection
*
* @param allowCached Determines if cached value can be used. Applies only to LazyCollectionRef.
* @return The collection state modeled in zookeeper
*/
public DocCollection get(boolean allowCached) {
gets.incrementAndGet();
return coll;
}
public boolean isLazilyLoaded() {
return false;
}
@Override
public String toString() {
if (coll != null) {
return coll.toString();
} else {
return "null DocCollection ref";
}
}
}
public int size() {
return collectionStates.size();
}
private static volatile Function<JSONParser, ObjectBuilder> STR_INTERNER_OBJ_BUILDER =
STANDARDOBJBUILDER;
public static void setStrInternerParser(Function<JSONParser, ObjectBuilder> fun) {
if (fun == null) return;
STR_INTERNER_OBJ_BUILDER = fun;
}
}