blob: 802299a5d544c052b1347feac7ea06c39b5ce3f4 [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 java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.solr.common.cloud.Replica.Type;
import org.noggit.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.common.util.Utils.toJSONString;
/**
* A Slice contains immutable information about a logical shard (all replicas that share the same shard id).
*/
public class Slice extends ZkNodeProps implements Iterable<Replica> {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public final String collection;
/** Loads multiple slices into a Map from a generic Map that probably came from deserialized JSON. */
@SuppressWarnings({"unchecked"})
public static Map<String,Slice> loadAllFromMap(String collection, Map<String, Object> genericSlices) {
if (genericSlices == null) return Collections.emptyMap();
Map<String, Slice> result = new LinkedHashMap<>(genericSlices.size());
for (Map.Entry<String, Object> entry : genericSlices.entrySet()) {
String name = entry.getKey();
Object val = entry.getValue();
if (val instanceof Slice) {
result.put(name, (Slice) val);
} else if (val instanceof Map) {
result.put(name, new Slice(name, null, (Map<String, Object>) val, collection));
}
}
return result;
}
@Override
public Iterator<Replica> iterator() {
return replicas.values().iterator();
}
/**Make a copy with a modified replica
*/
public Slice copyWith(Replica modified) {
log.debug("modified replica : {}", modified);
Map<String, Replica> replicasCopy = new LinkedHashMap<>(replicas);
replicasCopy.put(modified.getName(), modified);
return new Slice(name, replicasCopy, propMap, collection);
}
/** The slice's state. */
public enum State {
/** The normal/default state of a shard. */
ACTIVE,
/**
* A shard is put in that state after it has been successfully split. See
* <a href="https://lucene.apache.org/solr/guide/collections-api.html#splitshard">
* the reference guide</a> for more details.
*/
INACTIVE,
/**
* When a shard is split, the new sub-shards are put in that state while the
* split operation is in progress. It's also used when the shard is undergoing data restoration.
* A shard in this state still receives
* update requests from the parent shard leader, however does not participate
* in distributed search.
*/
CONSTRUCTION,
/**
* Sub-shards of a split shard are put in that state, when they need to
* create replicas in order to meet the collection's replication factor. A
* shard in that state still receives update requests from the parent shard
* leader, however does not participate in distributed search.
*/
RECOVERY,
/**
* Sub-shards of a split shard are put in that state when the split is deemed failed
* by the overseer even though all replicas are active because either the leader node is
* no longer live or has a different ephemeral owner (zk session id). Such conditions can potentially
* lead to data loss. See SOLR-9438 for details. A shard in that state will neither receive
* update requests from the parent shard leader, nor participate in distributed search.
*/
RECOVERY_FAILED;
@Override
public String toString() {
return super.toString().toLowerCase(Locale.ROOT);
}
/** Converts the state string to a State instance. */
public static State getState(String stateStr) {
return Slice.State.valueOf(stateStr.toUpperCase(Locale.ROOT));
}
}
public static final String REPLICAS = "replicas";
public static final String RANGE = "range";
public static final String LEADER = "leader"; // FUTURE: do we want to record the leader as a slice property in the JSON (as opposed to isLeader as a replica property?)
public static final String PARENT = "parent";
private final String name;
private final DocRouter.Range range;
private final Integer replicationFactor; // FUTURE: optional per-slice override of the collection replicationFactor
private final Map<String,Replica> replicas;
private final Replica leader;
private final State state;
private final String parent;
private final Map<String, RoutingRule> routingRules;
/**
* @param name The name of the slice
* @param replicas The replicas of the slice. This is used directly and a copy is not made. If null, replicas will be constructed from props.
* @param props The properties of the slice - a shallow copy will always be made.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public Slice(String name, Map<String,Replica> replicas, Map<String,Object> props, String collection) {
super( props==null ? new LinkedHashMap<String,Object>(2) : new LinkedHashMap<>(props));
this.name = name;
this.collection = collection;
Object rangeObj = propMap.get(RANGE);
if (propMap.get(ZkStateReader.STATE_PROP) != null) {
this.state = Slice.State.getState((String) propMap.get(ZkStateReader.STATE_PROP));
} else {
this.state = Slice.State.ACTIVE; //Default to ACTIVE
propMap.put(ZkStateReader.STATE_PROP, state.toString());
}
DocRouter.Range tmpRange = null;
if (rangeObj instanceof DocRouter.Range) {
tmpRange = (DocRouter.Range)rangeObj;
} else if (rangeObj != null) {
// Doesn't support custom implementations of Range, but currently not needed.
tmpRange = DocRouter.DEFAULT.fromString(rangeObj.toString());
}
range = tmpRange;
/** debugging. this isn't an error condition for custom sharding.
if (range == null) {
System.out.println("###### NO RANGE for " + name + " props=" + props);
}
**/
if (propMap.containsKey(PARENT) && propMap.get(PARENT) != null)
this.parent = (String) propMap.get(PARENT);
else
this.parent = null;
replicationFactor = null; // future
// add the replicas *after* the other properties (for aesthetics, so it's easy to find slice properties in the JSON output)
this.replicas = replicas != null ? replicas : makeReplicas(collection,name, (Map<String,Object>)propMap.get(REPLICAS));
propMap.put(REPLICAS, this.replicas);
Map<String, Object> rules = (Map<String, Object>) propMap.get("routingRules");
if (rules != null) {
this.routingRules = new HashMap<>();
for (Map.Entry<String, Object> entry : rules.entrySet()) {
Object o = entry.getValue();
if (o instanceof Map) {
Map map = (Map) o;
RoutingRule rule = new RoutingRule(entry.getKey(), map);
routingRules.put(entry.getKey(), rule);
} else {
routingRules.put(entry.getKey(), (RoutingRule) o);
}
}
} else {
this.routingRules = null;
}
leader = findLeader();
}
@SuppressWarnings({"unchecked"})
private Map<String,Replica> makeReplicas(String collection, String slice,Map<String,Object> genericReplicas) {
if (genericReplicas == null) return new HashMap<>(1);
Map<String,Replica> result = new LinkedHashMap<>(genericReplicas.size());
for (Map.Entry<String,Object> entry : genericReplicas.entrySet()) {
String name = entry.getKey();
Object val = entry.getValue();
Replica r;
if (val instanceof Replica) {
r = (Replica)val;
} else {
r = new Replica(name, (Map<String,Object>)val, collection, slice);
}
result.put(name, r);
}
return result;
}
private Replica findLeader() {
for (Replica replica : replicas.values()) {
if (replica.isLeader()) {
assert replica.getType() == Type.TLOG || replica.getType() == Type.NRT: "Pull replica should not become leader!";
return replica;
}
}
return null;
}
public String getCollection() {
return collection;
}
/**
* Return slice name (shard id).
*/
public String getName() {
return name;
}
/**
* Gets the list of all replicas for this slice.
*/
public Collection<Replica> getReplicas() {
return replicas.values();
}
public Set<String> getReplicaNames() {
return Collections.unmodifiableSet(replicas.keySet());
}
/**
* Gets all replicas that match a predicate
*/
public List<Replica> getReplicas(Predicate<Replica> pred) {
return replicas.values().stream().filter(pred).collect(Collectors.toList());
}
/**
* Gets the list of replicas that have a type present in s
*/
public List<Replica> getReplicas(EnumSet<Replica.Type> s) {
return this.getReplicas(r->s.contains(r.getType()));
}
/**
* Get the map of coreNodeName to replicas for this slice.
*/
public Map<String, Replica> getReplicasMap() {
return replicas;
}
public Map<String,Replica> getReplicasCopy() {
return new LinkedHashMap<>(replicas);
}
public Replica getLeader() {
return leader;
}
public Replica getReplica(String replicaName) {
return replicas.get(replicaName);
}
public DocRouter.Range getRange() {
return range;
}
public State getState() {
return state;
}
public String getParent() {
return parent;
}
public Map<String, RoutingRule> getRoutingRules() {
return routingRules;
}
@Override
public String toString() {
return name + ':' + toJSONString(propMap);
}
@Override
public void write(JSONWriter jsonWriter) {
jsonWriter.write(propMap);
}
}