Rewrite ClusterAllocationExplanation to be more object-y

This commit is contained in:
Lee Hinman 2016-04-25 11:44:30 -06:00
parent 1987107d75
commit f4d688a2c4
1 changed files with 314 additions and 125 deletions

View File

@ -52,28 +52,34 @@ public final class ClusterAllocationExplanation implements ToXContent, Writeable
private final ShardId shard;
private final boolean primary;
private final String assignedNodeId;
private final Map<DiscoveryNode, Decision> nodeToDecision;
private final Map<DiscoveryNode, Float> nodeWeights;
private final UnassignedInfo unassignedInfo;
private final long remainingDelayNanos;
private final List<IndicesShardStoresResponse.StoreStatus> shardStores;
private final Set<String> activeAllocationIds;
private Map<DiscoveryNode, IndicesShardStoresResponse.StoreStatus> nodeStoreStatus = null;
private final Map<DiscoveryNode, NodeExplanation> nodeExplanations;
public ClusterAllocationExplanation(ShardId shard, boolean primary, @Nullable String assignedNodeId,
UnassignedInfo unassignedInfo, Map<DiscoveryNode, Decision> nodeToDecision,
Map<DiscoveryNode, Float> nodeWeights, long remainingDelayNanos,
Map<DiscoveryNode, Float> nodeToWeight, long remainingDelayNanos,
List<IndicesShardStoresResponse.StoreStatus> shardStores, Set<String> activeAllocationIds) {
this.shard = shard;
this.primary = primary;
this.assignedNodeId = assignedNodeId;
this.unassignedInfo = unassignedInfo;
this.nodeToDecision = nodeToDecision == null ? Collections.emptyMap() : nodeToDecision;
this.nodeWeights = nodeWeights == null ? Collections.emptyMap() : nodeWeights;
this.remainingDelayNanos = remainingDelayNanos;
this.shardStores = shardStores;
this.activeAllocationIds = activeAllocationIds;
final Map<DiscoveryNode, Decision> nodeDecisions = nodeToDecision == null ? Collections.emptyMap() : nodeToDecision;
final Map<DiscoveryNode, Float> nodeWeights = nodeToWeight == null ? Collections.emptyMap() : nodeToWeight;
assert nodeDecisions.size() == nodeWeights.size() : "decision and weight list should be the same size";
final Map<DiscoveryNode, IndicesShardStoresResponse.StoreStatus> storeStatuses = calculateStoreStatuses(shardStores);
this.nodeExplanations = new HashMap<>(nodeDecisions.size());
for (Map.Entry<DiscoveryNode, Decision> entry : nodeDecisions.entrySet()) {
final DiscoveryNode node = entry.getKey();
final Decision decision = entry.getValue();
final NodeExplanation nodeExplanation = calculateNodeExplanation(node, decision, nodeWeights.get(node),
storeStatuses.get(node), assignedNodeId, activeAllocationIds);
nodeExplanations.put(node, nodeExplanation);
}
}
public ClusterAllocationExplanation(StreamInput in) throws IOException {
@ -81,45 +87,107 @@ public final class ClusterAllocationExplanation implements ToXContent, Writeable
this.primary = in.readBoolean();
this.assignedNodeId = in.readOptionalString();
this.unassignedInfo = in.readOptionalWriteable(UnassignedInfo::new);
this.remainingDelayNanos = in.readVLong();
Map<DiscoveryNode, Decision> ntd = null;
int size = in.readVInt();
ntd = new HashMap<>(size);
for (int i = 0; i < size; i++) {
DiscoveryNode dn = new DiscoveryNode(in);
Decision decision = Decision.readFrom(in);
ntd.put(dn, decision);
}
this.nodeToDecision = ntd;
Map<DiscoveryNode, Float> ntw = null;
size = in.readVInt();
ntw = new HashMap<>(size);
for (int i = 0; i < size; i++) {
DiscoveryNode dn = new DiscoveryNode(in);
float weight = in.readFloat();
ntw.put(dn, weight);
}
this.nodeWeights = ntw;
remainingDelayNanos = in.readVLong();
size = in.readVInt();
List<IndicesShardStoresResponse.StoreStatus> stores = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
stores.add(IndicesShardStoresResponse.StoreStatus.readStoreStatus(in));
}
this.shardStores = stores;
size = in.readVInt();
Set<String> activeIds = new HashSet<>(size);
for (int i = 0; i < size; i++) {
int allocIdSize = in.readVInt();
Set<String> activeIds = new HashSet<>(allocIdSize);
for (int i = 0; i < allocIdSize; i++) {
activeIds.add(in.readString());
}
this.activeAllocationIds = activeIds;
int mapSize = in.readVInt();
Map<DiscoveryNode, NodeExplanation> nodeToExplanation = new HashMap<>(mapSize);
for (int i = 0; i < mapSize; i++) {
NodeExplanation nodeExplanation = new NodeExplanation(in);
nodeToExplanation.put(nodeExplanation.getNode(), nodeExplanation);
}
this.nodeExplanations = nodeToExplanation;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
this.getShard().writeTo(out);
out.writeBoolean(this.isPrimary());
out.writeOptionalString(this.getAssignedNodeId());
out.writeOptionalWriteable(this.getUnassignedInfo());
out.writeVLong(remainingDelayNanos);
out.writeVInt(activeAllocationIds.size());
for (String id : activeAllocationIds) {
out.writeString(id);
}
out.writeVInt(this.nodeExplanations.size());
for (NodeExplanation explanation : this.nodeExplanations.values()) {
explanation.writeTo(out);
}
}
private NodeExplanation calculateNodeExplanation(DiscoveryNode node,
Decision nodeDecision,
Float nodeWeight,
IndicesShardStoresResponse.StoreStatus storeStatus,
String assignedNodeId,
Set<String> activeAllocationIds) {
FinalDecision finalDecision;
StoreCopy storeCopy;
String finalExplanation;
if (node.getId().equals(assignedNodeId)) {
finalDecision = FinalDecision.ALREADY_ASSIGNED;
finalExplanation = "the shard is already assigned to this node";
} else if (nodeDecision.type() == Decision.Type.NO) {
finalDecision = FinalDecision.NO;
finalExplanation = "the shard cannot be assigned because one or more allocation decider returns a 'NO' decision";
} else {
finalDecision = FinalDecision.YES;
finalExplanation = "the shard can be assigned";
}
if (storeStatus != null) {
final Throwable storeErr = storeStatus.getStoreException();
if (storeErr != null) {
finalDecision = FinalDecision.NO;
if (ExceptionsHelper.unwrapCause(storeErr) instanceof IOException) {
storeCopy = StoreCopy.IO_ERROR;
finalExplanation = "there was an IO error reading from data in the shard store";
} else {
storeCopy = StoreCopy.CORRUPT;
finalExplanation = "the copy of data in the shard store is corrupt";
}
} else if (activeAllocationIds.isEmpty() || activeAllocationIds.contains(storeStatus.getAllocationId())) {
// If either we don't have allocation IDs, or they contain the store allocation id, show the allocation
// status
storeCopy = StoreCopy.AVAILABLE;
finalExplanation = "the shard can be assigned and the node contains a valid copy of the shard data";
} else {
// Otherwise, this is a stale copy of the data (allocation ids don't match)
storeCopy = StoreCopy.STALE;
finalExplanation = "the copy of the shard is stale, allocation ids do not match";
finalDecision = FinalDecision.NO;
}
} else {
// No copies of the data, so deciders are what influence the decision and explanation
storeCopy = StoreCopy.NONE;
}
return new NodeExplanation(node, nodeDecision, nodeWeight, storeStatus, finalDecision, finalExplanation, storeCopy);
}
private static Map<DiscoveryNode, IndicesShardStoresResponse.StoreStatus> calculateStoreStatuses(
List<IndicesShardStoresResponse.StoreStatus> shardStores) {
Map<DiscoveryNode, IndicesShardStoresResponse.StoreStatus> nodeToStatus = new HashMap<>(shardStores.size());
for (IndicesShardStoresResponse.StoreStatus status : shardStores) {
nodeToStatus.put(status.getNode(), status);
}
return nodeToStatus;
}
/** Return the shard that the explanation is about */
public ShardId getShard() {
return this.shard;
}
/** Return true if the explained shard is primary, false otherwise */
public boolean isPrimary() {
return this.primary;
}
@ -141,41 +209,21 @@ public final class ClusterAllocationExplanation implements ToXContent, Writeable
return this.unassignedInfo;
}
/** Return a map of node to decision for shard allocation */
public Map<DiscoveryNode, Decision> getNodeDecisions() {
return this.nodeToDecision;
}
/**
* Return a map of node to balancer "weight" for allocation. Higher weights mean the balancer wants to allocated the shard to that node
* more
*/
public Map<DiscoveryNode, Float> getNodeWeights() {
return this.nodeWeights;
}
/** Return the remaining allocation delay for this shard in nanoseconds */
public long getRemainingDelayNanos() {
return this.remainingDelayNanos;
}
/** Return a map of {@code DiscoveryNode} to store status for the explained shard */
public Map<DiscoveryNode, IndicesShardStoresResponse.StoreStatus> getNodeStoreStatus() {
if (nodeStoreStatus == null) {
Map<DiscoveryNode, IndicesShardStoresResponse.StoreStatus> nodeToStatus = new HashMap<>(shardStores.size());
for (IndicesShardStoresResponse.StoreStatus status : shardStores) {
nodeToStatus.put(status.getNode(), status);
}
nodeStoreStatus = nodeToStatus;
}
return nodeStoreStatus;
}
/** Return a set of the active allocation ids for this shard */
public Set<String> getActiveAllocationIds() {
return this.activeAllocationIds;
}
/** Return a map of node to the explanation for that node */
public Map<DiscoveryNode, NodeExplanation> getNodeExplanations() {
return this.nodeExplanations;
}
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); {
builder.startObject("shard"); {
@ -200,45 +248,8 @@ public final class ClusterAllocationExplanation implements ToXContent, Writeable
builder.field("remaining_delay_ms", TimeValue.timeValueNanos(remainingDelayNanos).millis());
}
builder.startObject("nodes");
for (Map.Entry<DiscoveryNode, Float> entry : nodeWeights.entrySet()) {
DiscoveryNode node = entry.getKey();
builder.startObject(node.getId()); {
builder.field("node_name", node.getName());
builder.startObject("node_attributes"); {
for (Map.Entry<String, String> attrEntry : node.getAttributes().entrySet()) {
builder.field(attrEntry.getKey(), attrEntry.getValue());
}
}
builder.endObject(); // end attributes
Decision d = nodeToDecision.get(node);
String finalDecision = node.getId().equals(assignedNodeId) ? "CURRENTLY_ASSIGNED" : d.type().toString();
IndicesShardStoresResponse.StoreStatus storeStatus = getNodeStoreStatus().get(node);
builder.startObject("store"); {
if (storeStatus != null) {
final Throwable storeErr = storeStatus.getStoreException();
if (storeErr != null) {
builder.field("store_exception", ExceptionsHelper.detailedMessage(storeErr));
// Cannot allocate, final decision is "STORE_ERROR"
finalDecision = "STORE_ERROR";
}
if (activeAllocationIds.isEmpty() || activeAllocationIds.contains(storeStatus.getAllocationId())) {
// If either we don't have allocation IDs, or they contain the store allocation id, show the allocation
// status
builder.field("shard_copy", storeStatus.getAllocationStatus());
} else{
// Otherwise, this is a stale copy of the data (allocation ids don't match)
builder.field("shard_copy", "STALE_COPY");
// Cannot allocate, final decision is "STORE_STALE"
finalDecision = "STORE_STALE";
}
}
}
builder.endObject(); // end store
builder.field("final_decision", finalDecision);
builder.field("weight", entry.getValue());
d.toXContent(builder, params);
}
builder.endObject(); // end node <uuid>
for (NodeExplanation explanation : nodeExplanations.values()) {
explanation.toXContent(builder, params);
}
builder.endObject(); // end nodes
}
@ -246,33 +257,211 @@ public final class ClusterAllocationExplanation implements ToXContent, Writeable
return builder;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
this.getShard().writeTo(out);
out.writeBoolean(this.isPrimary());
out.writeOptionalString(this.getAssignedNodeId());
out.writeOptionalWriteable(this.getUnassignedInfo());
/** The cluster allocation explanation for a single node */
public class NodeExplanation implements Writeable, ToXContent {
private final DiscoveryNode node;
private final Decision nodeDecision;
private final Float nodeWeight;
private final IndicesShardStoresResponse.StoreStatus storeStatus;
private final FinalDecision finalDecision;
private final String finalExplanation;
private final StoreCopy storeCopy;
Map<DiscoveryNode, Decision> ntd = this.getNodeDecisions();
out.writeVInt(ntd.size());
for (Map.Entry<DiscoveryNode, Decision> entry : ntd.entrySet()) {
entry.getKey().writeTo(out);
Decision.writeTo(entry.getValue(), out);
public NodeExplanation(final DiscoveryNode node, final Decision nodeDecision, final Float nodeWeight,
final @Nullable IndicesShardStoresResponse.StoreStatus storeStatus,
final FinalDecision finalDecision, final String finalExplanation, final StoreCopy storeCopy) {
this.node = node;
this.nodeDecision = nodeDecision;
this.nodeWeight = nodeWeight;
this.storeStatus = storeStatus;
this.finalDecision = finalDecision;
this.finalExplanation = finalExplanation;
this.storeCopy = storeCopy;
}
Map<DiscoveryNode, Float> ntw = this.getNodeWeights();
out.writeVInt(ntw.size());
for (Map.Entry<DiscoveryNode, Float> entry : ntw.entrySet()) {
entry.getKey().writeTo(out);
out.writeFloat(entry.getValue());
public NodeExplanation(StreamInput in) throws IOException {
this.node = new DiscoveryNode(in);
this.nodeDecision = Decision.readFrom(in);
this.nodeWeight = in.readFloat();
if (in.readBoolean()) {
this.storeStatus = IndicesShardStoresResponse.StoreStatus.readStoreStatus(in);
} else {
this.storeStatus = null;
}
this.finalDecision = FinalDecision.readFrom(in);
this.finalExplanation = in.readString();
this.storeCopy = StoreCopy.readFrom(in);
}
out.writeVLong(remainingDelayNanos);
out.writeVInt(shardStores.size());
for (IndicesShardStoresResponse.StoreStatus status : shardStores) {
status.writeTo(out);
@Override
public void writeTo(StreamOutput out) throws IOException {
node.writeTo(out);
Decision.writeTo(nodeDecision, out);
out.writeFloat(nodeWeight);
if (storeStatus == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
storeStatus.writeTo(out);
}
finalDecision.writeTo(out);
out.writeString(finalExplanation);
storeCopy.writeTo(out);
}
out.writeVInt(activeAllocationIds.size());
for (String id : activeAllocationIds) {
out.writeString(id);
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(node.getId()); {
builder.field("node_name", node.getName());
builder.startObject("node_attributes"); {
for (Map.Entry<String, String> attrEntry : node.getAttributes().entrySet()) {
builder.field(attrEntry.getKey(), attrEntry.getValue());
}
}
builder.endObject(); // end attributes
builder.startObject("store"); {
builder.field("shard_copy", storeCopy.toString());
if (storeStatus != null) {
final Throwable storeErr = storeStatus.getStoreException();
if (storeErr != null) {
builder.field("store_exception", ExceptionsHelper.detailedMessage(storeErr));
}
}
}
builder.endObject(); // end store
builder.field("final_decision", finalDecision.toString());
builder.field("final_explanation", finalExplanation.toString());
builder.field("weight", nodeWeight);
nodeDecision.toXContent(builder, params);
}
builder.endObject(); // end node <uuid>
return builder;
}
public DiscoveryNode getNode() {
return this.node;
}
public Decision getDecision() {
return this.nodeDecision;
}
public Float getWeight() {
return this.nodeWeight;
}
@Nullable
public IndicesShardStoresResponse.StoreStatus getStoreStatus() {
return this.storeStatus;
}
public FinalDecision getFinalDecision() {
return this.finalDecision;
}
public String getFinalExplanation() {
return this.finalExplanation;
}
public StoreCopy getStoreCopy() {
return this.storeCopy;
}
}
/** An Enum representing the final decision for a shard allocation on a node */
public enum FinalDecision {
// Yes, the shard can be assigned
YES((byte) 0),
// No, the shard cannot be assigned
NO((byte) 1),
// The shard is already assigned to this node
ALREADY_ASSIGNED((byte) 2);
private final byte id;
FinalDecision (byte id) {
this.id = id;
}
private static FinalDecision fromId(byte id) {
switch (id) {
case 0: return YES;
case 1: return NO;
case 2: return ALREADY_ASSIGNED;
default:
throw new IllegalArgumentException("unknown id for final decision: [" + id + "]");
}
}
@Override
public String toString() {
switch (id) {
case 0: return "YES";
case 1: return "NO";
case 2: return "ALREADY_ASSIGNED";
default:
throw new IllegalArgumentException("unknown id for final decision: [" + id + "]");
}
}
static FinalDecision readFrom(StreamInput in) throws IOException {
return fromId(in.readByte());
}
void writeTo(StreamOutput out) throws IOException {
out.writeByte(id);
}
}
/** An Enum representing the state of the shard store's copy of the data on a node */
public enum StoreCopy {
// No data for this shard is on the node
NONE((byte) 0),
// A copy of the data is available on this node
AVAILABLE((byte) 1),
// The copy of the data on the node is corrupt
CORRUPT((byte) 2),
// There was an error reading this node's copy of the data
IO_ERROR((byte) 3),
// The copy of the data on the node is stale
STALE((byte) 4);
private final byte id;
StoreCopy (byte id) {
this.id = id;
}
private static StoreCopy fromId(byte id) {
switch (id) {
case 0: return NONE;
case 1: return AVAILABLE;
case 2: return CORRUPT;
case 3: return IO_ERROR;
case 4: return STALE;
default:
throw new IllegalArgumentException("unknown id for store copy: [" + id + "]");
}
}
@Override
public String toString() {
switch (id) {
case 0: return "NONE";
case 1: return "AVAILABLE";
case 2: return "CORRUPT";
case 3: return "IO_ERROR";
case 4: return "STALE";
default:
throw new IllegalArgumentException("unknown id for store copy: [" + id + "]");
}
}
static StoreCopy readFrom(StreamInput in) throws IOException {
return fromId(in.readByte());
}
void writeTo(StreamOutput out) throws IOException {
out.writeByte(id);
}
}
}