blob: cc17cf00128f1634cf284dae2c5f36635db1ca9f [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.slider.server.appmaster.state;
import org.apache.hadoop.yarn.api.records.Container;
import org.apache.hadoop.yarn.api.records.NodeReport;
import org.apache.hadoop.yarn.api.records.NodeState;
import org.apache.slider.api.types.NodeInformation;
import org.apache.slider.common.tools.Comparators;
import org.apache.slider.common.tools.SliderUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
/**
* A node instance -stores information about a node in the cluster.
* <p>
* Operations on the array/set of roles are synchronized.
*/
public class NodeInstance {
public final String hostname;
/**
* last state of node. Starts off as {@link NodeState#RUNNING},
* on the assumption that it is live.
*/
private NodeState nodeState = NodeState.RUNNING;
/**
* Last node report. If null: none
*/
private NodeReport nodeReport = null;
/**
* time of state update
*/
private long nodeStateUpdateTime = 0;
/**
* Node labels.
*
* IMPORTANT: we assume that there is one label/node, which is the policy
* for Hadoop as of November 2015
*/
private String nodeLabels = "";
/**
* An unordered list of node entries of specific roles. There's nothing
* indexed so as to support sparser datastructures.
*/
private final List<NodeEntry> nodeEntries;
/**
* Create an instance and the (empty) array of nodes
* @param roles role count -the no. of roles
*/
public NodeInstance(String hostname, int roles) {
this.hostname = hostname;
nodeEntries = new ArrayList<>(roles);
}
/**
* Update the node status.
* The return code is true if the node state changed enough to
* trigger a re-evaluation of pending requests. That is, either a node
* became available when it was previously not, or the label changed
* on an available node.
*
* Transitions of a node from live to dead aren't treated as significant,
* nor label changes on a dead node.
*
* @param report latest node report
* @return true if the node state changed enough for a request evaluation.
*/
public synchronized boolean updateNode(NodeReport report) {
nodeStateUpdateTime = report.getLastHealthReportTime();
nodeReport = report;
NodeState oldState = nodeState;
boolean oldStateUnusable = oldState.isUnusable();
nodeState = report.getNodeState();
boolean newUsable = !nodeState.isUnusable();
boolean nodeNowAvailable = oldStateUnusable && newUsable;
String labels = this.nodeLabels;
nodeLabels = SliderUtils.extractNodeLabel(report);
return nodeNowAvailable
|| newUsable && !this.nodeLabels.equals(labels);
}
public String getNodeLabels() {
return nodeLabels;
}
/**
* Get the entry for a role -if present
* @param role role index
* @return the entry
* null if the role is out of range
*/
public synchronized NodeEntry get(int role) {
for (NodeEntry nodeEntry : nodeEntries) {
if (nodeEntry.rolePriority == role) {
return nodeEntry;
}
}
return null;
}
/**
* Get the entry for a role -if present
* @param role role index
* @return the entry
* @throws ArrayIndexOutOfBoundsException if the role is out of range
*/
public synchronized NodeEntry getOrCreate(int role) {
NodeEntry entry = get(role);
if (entry == null) {
entry = new NodeEntry(role);
nodeEntries.add(entry);
}
return entry;
}
/**
* Get the node entry matching a container on this node
* @param container container
* @return matching node instance for the role
*/
public NodeEntry getOrCreate(Container container) {
return getOrCreate(ContainerPriority.extractRole(container));
}
/**
* Count the number of active role instances on this node
* @param role role index
* @return 0 if there are none, otherwise the #of nodes that are running and
* not being released already.
*/
public int getActiveRoleInstances(int role) {
NodeEntry nodeEntry = get(role);
return (nodeEntry != null ) ? nodeEntry.getActive() : 0;
}
/**
* Count the number of live role instances on this node
* @param role role index
* @return 0 if there are none, otherwise the #of nodes that are running
*/
public int getLiveRoleInstances(int role) {
NodeEntry nodeEntry = get(role);
return (nodeEntry != null ) ? nodeEntry.getLive() : 0;
}
/**
* Is the node considered online
* @return the node
*/
public boolean isOnline() {
return !nodeState.isUnusable();
}
/**
* Query for a node being considered unreliable
* @param role role key
* @param threshold threshold above which a node is considered unreliable
* @return true if the node is considered unreliable
*/
public boolean isConsideredUnreliable(int role, int threshold) {
NodeEntry entry = get(role);
return entry != null && entry.getFailedRecently() > threshold;
}
/**
* Get the entry for a role -and remove it if present
* @param role the role index
* @return the entry that WAS there
*/
public synchronized NodeEntry remove(int role) {
NodeEntry nodeEntry = get(role);
if (nodeEntry != null) {
nodeEntries.remove(nodeEntry);
}
return nodeEntry;
}
public synchronized void set(int role, NodeEntry nodeEntry) {
remove(role);
nodeEntries.add(nodeEntry);
}
/**
* run through each entry; gc'ing & removing old ones that don't have
* a recent failure count (we care about those)
* @param absoluteTime age in millis
* @return true if there are still entries left
*/
public synchronized boolean purgeUnusedEntries(long absoluteTime) {
boolean active = false;
ListIterator<NodeEntry> entries = nodeEntries.listIterator();
while (entries.hasNext()) {
NodeEntry entry = entries.next();
if (entry.notUsedSince(absoluteTime) && entry.getFailedRecently() == 0) {
entries.remove();
} else {
active = true;
}
}
return active;
}
/**
* run through each entry resetting the failure count
*/
public synchronized void resetFailedRecently() {
for (NodeEntry entry : nodeEntries) {
entry.resetFailedRecently();
}
}
@Override
public String toString() {
return hostname;
}
/**
* Full dump of entry including children
* @return a multi-line description fo the node
*/
public String toFullString() {
final StringBuilder sb =
new StringBuilder(toString());
sb.append("{ ");
for (NodeEntry entry : nodeEntries) {
sb.append(String.format("\n [%02d] ", entry.rolePriority));
sb.append(entry.toString());
}
sb.append("} ");
return sb.toString();
}
/**
* Equality test is purely on the hostname of the node address
* @param o other
* @return true if the hostnames are equal
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NodeInstance that = (NodeInstance) o;
return hostname.equals(that.hostname);
}
@Override
public int hashCode() {
return hostname.hashCode();
}
/**
* Predicate to query if the number of recent failures of a role
* on this node exceeds that role's failure threshold.
* If there is no record of a deployment of that role on this
* node, the failure count is taken as "0".
* @param role role to look up
* @return true if the failure rate is above the threshold.
*/
public boolean exceedsFailureThreshold(RoleStatus role) {
NodeEntry entry = get(role.getKey());
int numFailuresOnLastHost = entry != null ? entry.getFailedRecently() : 0;
int failureThreshold = role.getNodeFailureThreshold();
return failureThreshold < 0 || numFailuresOnLastHost > failureThreshold;
}
/**
* Produced a serialized form which can be served up as JSON
* @param naming map of priority -> value for naming entries
* @return a summary of the current role status.
*/
public synchronized NodeInformation serialize(Map<Integer, String> naming) {
NodeInformation info = new NodeInformation();
info.hostname = hostname;
// null-handling state constructor
info.state = "" + nodeState;
info.lastUpdated = nodeStateUpdateTime;
info.labels = nodeLabels;
if (nodeReport != null) {
info.httpAddress = nodeReport.getHttpAddress();
info.rackName = nodeReport.getRackName();
info.healthReport = nodeReport.getHealthReport();
}
info.entries = new HashMap<>(nodeEntries.size());
for (NodeEntry nodeEntry : nodeEntries) {
String name = naming.get(nodeEntry.rolePriority);
if (name == null) {
name = Integer.toString(nodeEntry.rolePriority);
}
info.entries.put(name, nodeEntry.serialize());
}
return info;
}
/**
* Is this node instance a suitable candidate for the specific role?
* @param role role ID
* @param label label which must match, or "" for no label checks
* @return true if the node has space for this role, is running and the labels
* match.
*/
public boolean canHost(int role, String label) {
return isOnline()
&& (SliderUtils.isUnset(label) || label.equals(nodeLabels)) // label match
&& getOrCreate(role).isAvailable(); // no live role
}
/**
* A comparator for sorting entries where the node is preferred over another.
*
* The exact algorithm may change: current policy is "most recent first", so sorted
* on the lastUsed
*
* the comparision is a positive int if left is preferred to right;
* negative if right over left, 0 for equal
*/
public static class Preferred implements Comparator<NodeInstance>, Serializable {
private static final Comparators.InvertedLongComparator comparator =
new Comparators.InvertedLongComparator();
private final int role;
public Preferred(int role) {
this.role = role;
}
@Override
public int compare(NodeInstance o1, NodeInstance o2) {
NodeEntry left = o1.get(role);
NodeEntry right = o2.get(role);
long ageL = left != null ? left.getLastUsed() : -1;
long ageR = right != null ? right.getLastUsed() : -1;
return comparator.compare(ageL, ageR);
}
}
/**
* A comparator for sorting entries where the role is newer than
* the other.
* This sort only compares the lastUsed field, not whether the
* node is in use or not
*/
public static class MoreActiveThan implements Comparator<NodeInstance>,
Serializable {
private final int role;
public MoreActiveThan(int role) {
this.role = role;
}
@Override
public int compare(NodeInstance left, NodeInstance right) {
int activeLeft = left.getActiveRoleInstances(role);
int activeRight = right.getActiveRoleInstances(role);
return activeRight - activeLeft;
}
}
/**
* A comparator for sorting entries alphabetically
*/
public static class CompareNames implements Comparator<NodeInstance>,
Serializable {
public CompareNames() {
}
@Override
public int compare(NodeInstance left, NodeInstance right) {
return left.hostname.compareTo(right.hostname);
}
}
}