/**
* Licensed to Cloudera, Inc. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Cloudera, Inc. 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 com.cloudera.flume.master;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudera.flume.conf.FlumeConfiguration;
import com.cloudera.flume.reporter.ReportEvent;
import com.cloudera.flume.reporter.Reportable;
import com.cloudera.util.Clock;
/**
* This manages the last seen and heartbeat data on the master flume
* configuration node.
*
* There should be no references to Thrift here.
*/
public class StatusManager implements Reportable {
static final Logger LOG = LoggerFactory.getLogger(StatusManager.class);
public static class NodeStatus {
public NodeState state;
public long version;
public long lastseen;
public String host;
public String physicalNode;
public NodeStatus(NodeState state, long version, long lastseen,
String host, String physicalNode) {
this.state = state;
this.version = version;
this.lastseen = lastseen;
this.physicalNode = physicalNode;
this.host = host;
}
@Override
public String toString() {
return "node status (state " + state + ", version " + new Date(version)
+ ", lastseen " + new Date(lastseen) + ", host " + host + ")";
}
}
public enum NodeState {
HELLO, IDLE, CONFIGURING, ACTIVE, ERROR, LOST, DECOMMISSIONED
};
// runtime state of the flume system
final Map<String, NodeStatus> statuses = new HashMap<String, NodeStatus>();
public boolean updateHeartbeatStatus(String host, String physicalNode,
String logicalNode, NodeState stat, long version) {
LOG.debug("Heartbeat from host:" + host + " logicalnode:" + logicalNode
+ " (" + stat + "," + new Date(version) + ")");
boolean configChanged = false;
synchronized (statuses) {
String expectedPhys = FlumeMaster.getInstance().getSpecMan()
.getPhysicalNode(logicalNode);
if (expectedPhys == null || !expectedPhys.equals(physicalNode)) {
stat = NodeState.DECOMMISSIONED;
}
NodeStatus status = statuses.get(logicalNode);
if (status == null) {
status = new NodeStatus(stat, version, Clock.unixTime(), "", "");
configChanged = true;
}
status.state = stat;
status.version = version;
status.lastseen = Clock.unixTime();
status.host = host;
status.physicalNode = physicalNode;
statuses.put(logicalNode, status);
}
return configChanged;
}
/**
* checks to see if any node's last heart beat was too long ago
*/
public void checkup() {
long now = Clock.unixTime();
HashMap<String, NodeStatus> ss = null;
synchronized (statuses) {
ss = new HashMap<String, NodeStatus>(statuses);
}
// max out to missing 10 heart beats
int maxMissed = FlumeConfiguration.get().getMasterMaxMissedheartbeats();
long timeout = FlumeConfiguration.get().getConfigHeartbeatPeriod()
* maxMissed;
for (Entry<String, NodeStatus> e : ss.entrySet()) {
NodeStatus ns = e.getValue();
long delta = now - ns.lastseen;
if (delta > timeout) {
ns.state = NodeState.LOST;
}
// Check mapping exists, if it doesn't set status to DECOMMISSIONED
String expectedPhys = FlumeMaster.getInstance().getSpecMan()
.getPhysicalNode(e.getKey());
if (expectedPhys == null || !expectedPhys.equals(ns.physicalNode)) {
ns.state = NodeState.DECOMMISSIONED;
}
}
}
@Override
public String getName() {
return "Status";
}
/**
* TODO (jon) convert to a report
*/
@Override
public ReportEvent getReport() {
StringBuilder status = new StringBuilder();
status.append("<div class=\"StatusManager\">");
status.append("<h2>Node status</h2>\n<table border=\"1\">"
+ "<tr><th>logical node</th><th>physical node</th><th>host name</th>"
+ "<th>status</th><th>version</th><th>last seen delta (s)</th>"
+ "<th>last seen</th></tr>");
long now = Clock.unixTime();
for (Entry<String, NodeStatus> e : getNodeStatuses().entrySet()) {
status.append("\n<tr>");
NodeStatus v = e.getValue();
String version = (v.version == 0) ? "none" : new Date(v.version)
.toString();
status.append("<td>" + e.getKey() + "</td>");
status.append("<td>" + v.physicalNode + "</td>");
status.append("<td>" + v.host + "</td>");
status.append("<td>" + v.state + "</td>");
status.append("<td>" + version + "</td>");
status.append("<td>" + ((now - v.lastseen) / 1000));
status.append("<td>" + new Date(v.lastseen) + "</td>");
status.append("</tr>");
}
status.append("</table>");
status.append("</div>");
return ReportEvent.createLegacyHtmlReport("", status.toString());
}
/**
* Returns a copy of the hashmap containing the mapping from node names to
* their status object.
*/
public Map<String, NodeStatus> getNodeStatuses() {
HashMap<String, NodeStatus> ss = null;
synchronized (statuses) {
ss = new HashMap<String, NodeStatus>(statuses);
}
return ss;
}
/**
* Returns the NodeStatus of for the given node name
*/
public NodeStatus getStatus(String node) {
synchronized (statuses) {
return statuses.get(node);
}
}
}