blob: 76539e70ead60521d705d7bbcc4746ba3c7b8755 [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.toJSONString;
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;
/**
* 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;
private DocCollection.PrsSupplier prsSupplier;
void setPrsSupplier(DocCollection.PrsSupplier prsSupplier) {
this.prsSupplier = prsSupplier;
for (Replica r : replicas.values()) {
r.setPrsSupplier(prsSupplier);
}
if (leader == null) {
leader = findLeader();
}
}
/**
* 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) {
if (log.isDebugEnabled()) {
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://solr.apache.org/guide/solr/latest/deployment-guide/shard-management.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 State.valueOf(stateStr.toUpperCase(Locale.ROOT));
}
}
private final String name;
private final DocRouter.Range range;
// FUTURE: optional per-slice override of the collection replicationFactor
private final Integer replicationFactor;
private final Map<String, Replica> replicas;
private Replica leader;
private final State state;
private final String parent;
private final Map<String, RoutingRule> routingRules;
private final int numLeaderReplicas;
/**
* @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<>(2) : new LinkedHashMap<>(props));
this.name = name;
this.collection = collection;
Object rangeObj = propMap.get(SliceStateProps.RANGE);
if (propMap.get(SliceStateProps.STATE_PROP) != null) {
this.state = State.getState((String) propMap.get(SliceStateProps.STATE_PROP));
} else {
this.state = State.ACTIVE; // Default to ACTIVE
propMap.put(SliceStateProps.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(SliceStateProps.PARENT) && propMap.get(SliceStateProps.PARENT) != null)
this.parent = (String) propMap.get(SliceStateProps.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(SliceStateProps.REPLICAS));
propMap.put(SliceStateProps.REPLICAS, this.replicas);
this.numLeaderReplicas =
(int) this.replicas.values().stream().filter(r -> r.type.leaderEligible).count();
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;
}
}
@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() {
if (prsSupplier != null) {
// this is a PRS collection. leader may keep changing
return findLeader();
} else {
if (leader == null) {
leader = findLeader();
}
return leader;
}
}
public int getNumLeaderReplicas() {
return numLeaderReplicas;
}
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);
}
/** JSON properties related to a slice's state. */
public interface SliceStateProps {
String STATE_PROP = "state";
String REPLICAS = "replicas";
String RANGE = "range";
// FUTURE: do we want to record the leader as a slice property in the JSON (as opposed to
// isLeader
// as a replica property?)
String LEADER = "leader";
String PARENT = "parent";
}
}