blob: dc90e6b975a3dad6ec31665aeb45aa33e1f49173 [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.ConditionalMapWriter.NON_NULL_VAL;
import static org.apache.solr.common.ConditionalMapWriter.dedupeKeyPredicate;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiPredicate;
import org.apache.solr.common.MapWriter;
import org.apache.solr.common.util.Utils;
import org.noggit.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Replica extends ZkNodeProps implements MapWriter {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/**
* The replica's state. In general, if the node the replica is hosted on is not under {@code
* /live_nodes} in ZK, the replica's state should be discarded.
*/
public enum State {
/**
* The replica is ready to receive updates and queries.
*
* <p><b>NOTE</b>: when the node the replica is hosted on crashes, the replica's state may
* remain ACTIVE in ZK. To determine if the replica is truly active, you must also verify that
* its {@link Replica#getNodeName() node} is under {@code /live_nodes} in ZK (or use {@link
* ClusterState#liveNodesContain(String)}).
*/
ACTIVE("A"),
/**
* The first state before {@link State#RECOVERING}. A node in this state should be actively
* trying to move to {@link State#RECOVERING}.
*
* <p><b>NOTE</b>: a replica's state may appear DOWN in ZK also when the node it's hosted on
* gracefully shuts down. This is a best effort though, and should not be relied on.
*/
DOWN("D"),
/**
* The node is recovering from the leader. This might involve peer-sync, full replication or
* finding out things are already in sync.
*/
RECOVERING("R"),
/**
* Recovery attempts have not worked, something is not right.
*
* <p><b>NOTE</b>: This state doesn't matter if the node is not part of {@code /live_nodes} in
* ZK; in that case the node is not part of the cluster and it's state should be discarded.
*/
RECOVERY_FAILED("F");
/**
* short name for a state. Used to encode this in the state node see {@link
* PerReplicaStates.State}
*/
public final String shortName;
State(String c) {
this.shortName = c;
}
@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 stateStr == null ? null : State.valueOf(stateStr.toUpperCase(Locale.ROOT));
}
}
public enum Type {
/**
* Writes updates to transaction log and indexes locally. Replicas of type {@link Type#NRT}
* support NRT (soft commits) and RTG. Any {@link Type#NRT} replica can become a leader. A shard
* leader will forward updates to all active {@link Type#NRT} and {@link Type#TLOG} replicas.
*/
NRT(true),
/**
* Writes to transaction log, but not to index, uses replication. Any {@link Type#TLOG} replica
* can become leader (by first applying all local transaction log elements). If a replica is of
* type {@link Type#TLOG} but is also the leader, it will behave as a {@link Type#NRT}. A shard
* leader will forward updates to all active {@link Type#NRT} and {@link Type#TLOG} replicas.
*/
TLOG(true),
/**
* Doesn’t index or writes to transaction log. Just replicates from {@link Type#NRT} or {@link
* Type#TLOG} replicas. {@link Type#PULL} replicas can’t become shard leaders (i.e., if there
* are only pull replicas in the collection at some point, updates will fail same as if there is
* no leaders, queries continue to work), so they don’t even participate in elections.
*/
PULL(false);
public final boolean leaderEligible;
Type(boolean b) {
this.leaderEligible = b;
}
public static Type get(String name) {
return name == null ? Type.NRT : Type.valueOf(name.toUpperCase(Locale.ROOT));
}
}
// immutable
public final String name; // coreNode name
public final String node;
public final String core;
public final Type type;
public final String shard, collection;
private DocCollection.PrsSupplier prsSupplier;
// mutable
private State state;
void setPrsSupplier(DocCollection.PrsSupplier prsSupplier) {
this.prsSupplier = prsSupplier;
}
public Replica(String name, Map<String, Object> map, String collection, String shard) {
super(new HashMap<>());
propMap.putAll(map);
this.collection = collection;
this.shard = shard;
this.name = name;
this.node = (String) propMap.get(ReplicaStateProps.NODE_NAME);
this.core = (String) propMap.get(ReplicaStateProps.CORE_NAME);
this.type = Type.get((String) propMap.get(ReplicaStateProps.TYPE));
// default to ACTIVE
this.state =
State.getState(
String.valueOf(propMap.getOrDefault(ReplicaStateProps.STATE, State.ACTIVE.toString())));
validate();
}
// clone constructor
public Replica(
String name,
String node,
String collection,
String shard,
String core,
State state,
Type type,
Map<String, Object> props) {
super(new HashMap<>());
this.name = name;
this.node = node;
this.state = state;
this.type = type;
this.collection = collection;
this.shard = shard;
this.core = core;
if (props != null) {
this.propMap.putAll(props);
}
validate();
}
/**
* This constructor uses a map with one key (coreNode name) and a value that is a map containing
* all replica properties.
*
* @param nestedMap nested map containing replica properties
*/
@SuppressWarnings("unchecked")
public Replica(Map<String, Object> nestedMap) {
this.name = nestedMap.keySet().iterator().next();
Map<String, Object> details = (Map<String, Object>) nestedMap.get(name);
Objects.requireNonNull(details);
details = Utils.getDeepCopy(details, 4);
this.collection = String.valueOf(details.get("collection"));
this.shard = String.valueOf(details.get("shard"));
this.core = String.valueOf(details.get("core"));
this.node = String.valueOf(details.get("node_name"));
this.propMap.putAll(details);
type =
Replica.Type.valueOf(String.valueOf(propMap.getOrDefault(ReplicaStateProps.TYPE, "NRT")));
if (state == null)
state =
State.getState(String.valueOf(propMap.getOrDefault(ReplicaStateProps.STATE, "active")));
validate();
}
private final void validate() {
Objects.requireNonNull(this.name, "'name' must not be null");
Objects.requireNonNull(this.core, "'core' must not be null");
Objects.requireNonNull(this.collection, "'collection' must not be null");
Objects.requireNonNull(this.shard, "'shard' must not be null");
Objects.requireNonNull(this.type, "'type' must not be null");
Objects.requireNonNull(this.state, "'state' must not be null");
Objects.requireNonNull(this.node, "'node' must not be null");
String baseUrl = (String) propMap.get(ReplicaStateProps.BASE_URL);
Objects.requireNonNull(baseUrl, "'base_url' must not be null");
// make sure all declared props are in the propMap
propMap.put(ReplicaStateProps.COLLECTION, collection);
propMap.put(ReplicaStateProps.SHARD_ID, shard);
propMap.put(ReplicaStateProps.CORE_NODE_NAME, name);
propMap.put(ReplicaStateProps.NODE_NAME, node);
propMap.put(ReplicaStateProps.CORE_NAME, core);
propMap.put(ReplicaStateProps.TYPE, type.toString());
propMap.put(ReplicaStateProps.STATE, state.toString());
}
public String getCollection() {
return collection;
}
public String getShard() {
return shard;
}
@Override
public Map<String, Object> getProperties() {
return super.getProperties();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Replica other = (Replica) o;
return name.equals(other.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
/** Also known as coreNodeName. */
public String getName() {
return name;
}
public String getCoreUrl() {
return ZkCoreNodeProps.getCoreUrl(getBaseUrl(), core);
}
public String getBaseUrl() {
return getStr(ReplicaStateProps.BASE_URL);
}
/** SolrCore name. */
public String getCoreName() {
return core;
}
/** The name of the node this replica resides on */
public String getNodeName() {
return node;
}
/** Returns the {@link State} of this replica. */
public State getState() {
if (prsSupplier != null) {
PerReplicaStates.State s = prsSupplier.get().get(name);
if (s != null) {
return s.state;
} else {
return State.DOWN;
}
}
return state;
}
public void setState(State state) {
this.state = state;
propMap.put(ReplicaStateProps.STATE, this.state.toString());
}
public boolean isActive(Set<String> liveNodes) {
return this.node != null && liveNodes.contains(this.node) && getState() == State.ACTIVE;
}
public Type getType() {
return this.type;
}
public boolean isLeader() {
if (prsSupplier != null) {
PerReplicaStates.State st = prsSupplier.get().get(name);
return st == null ? false : st.isLeader;
}
return getBool(ReplicaStateProps.LEADER, false);
}
public Object get(String key, Object defValue) {
Object o = get(key);
if (o != null) {
return o;
} else {
return defValue;
}
}
public String getProperty(String propertyName) {
final String propertyKey;
if (!propertyName.startsWith(ReplicaStateProps.PROPERTY_PREFIX)) {
propertyKey = ReplicaStateProps.PROPERTY_PREFIX + propertyName;
} else {
propertyKey = propertyName;
}
final String propertyValue = getStr(propertyKey);
return propertyValue;
}
public Replica copyWith(PerReplicaStates.State state) {
log.debug("A replica is updated with new state : {}", state);
Map<String, Object> props = new LinkedHashMap<>(propMap);
if (state == null) {
props.put(ReplicaStateProps.STATE, State.DOWN.toString());
props.remove(ReplicaStateProps.LEADER);
} else {
props.put(ReplicaStateProps.STATE, state.state.toString());
if (state.isLeader) props.put(ReplicaStateProps.LEADER, "true");
}
Replica r = new Replica(name, props, collection, shard);
return r;
}
public PerReplicaStates.State getReplicaState() {
if (prsSupplier != null) {
return prsSupplier.get().get(name);
}
return null;
}
public Object clone() {
return new Replica(name, node, collection, shard, core, getState(), type, propMap);
}
@Override
public void writeMap(MapWriter.EntryWriter ew) throws IOException {
ew.put(name, _allPropsWriter());
}
private static final Map<String, State> STATES = new HashMap<>();
static {
STATES.put(Replica.State.ACTIVE.shortName, Replica.State.ACTIVE);
STATES.put(Replica.State.DOWN.shortName, Replica.State.DOWN);
STATES.put(Replica.State.RECOVERING.shortName, Replica.State.RECOVERING);
STATES.put(Replica.State.RECOVERY_FAILED.shortName, Replica.State.RECOVERY_FAILED);
}
public static State getState(String shortName) {
return STATES.get(shortName);
}
private MapWriter _allPropsWriter() {
BiPredicate<CharSequence, Object> p = dedupeKeyPredicate(new HashSet<>()).and(NON_NULL_VAL);
return writer -> {
// XXX this is why this class should be immutable - it's a mess !!!
// propMap takes precedence because it's mutable and we can't control its
// contents, so a third party may override some declared fields
for (Map.Entry<String, Object> e : propMap.entrySet()) {
writer.put(e.getKey(), e.getValue(), p);
}
writer
.put(ReplicaStateProps.CORE_NAME, core, p)
.put(ReplicaStateProps.SHARD_ID, shard, p)
.put(ReplicaStateProps.COLLECTION, collection, p)
.put(ReplicaStateProps.NODE_NAME, node, p)
.put(ReplicaStateProps.TYPE, type.toString(), p)
.put(ReplicaStateProps.STATE, shard, p);
};
}
@Override
public void write(JSONWriter jsonWriter) {
Map<String, Object> map = new LinkedHashMap<>();
// this serializes also our declared properties
_allPropsWriter().toMap(map);
jsonWriter.write(map);
}
@Override
public String toString() {
return name
+ ':'
+ Utils.toJSONString(propMap); // small enough, keep it on one line (i.e. no indent)
}
/** JSON properties related to a replica's state. */
public interface ReplicaStateProps {
String COLLECTION = "collection";
String SHARD_ID = "shard";
String LEADER = "leader";
String STATE = "state";
String CORE_NAME = "core";
String CORE_NODE_NAME = "core_node_name";
String TYPE = "type";
String NODE_NAME = "node_name";
String BASE_URL = "base_url";
String PROPERTY_PREFIX = "property.";
}
}