/**
* Copyright (c) 2011-2014, OpenIoT
*
* This file is part of OpenIoT.
*
* OpenIoT is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* OpenIoT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with OpenIoT. If not, see <http://www.gnu.org/licenses/>.
*
* Contact: OpenIoT mailto: info@openiot.eu
* @author Sofiane Sarni
*/
package org.openiot.gsn.utils;
import org.openiot.gsn.beans.GSNSessionAddress;
import org.openiot.gsn.beans.VSensorMonitorConfig;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/*
* GSN Monitor
* Monitors a series of Virtual Sensors (last update time)
* and generates a short report shown on standard output
* You can put this program in a Cron job and run it regularely to check status.
* It can also be used from Nagios
* Syntax for calling:
* java -jar VSMonitor.jar <config_file>
* e.g. java -jar VSMonitor.jar conf/monitoring.cfg
* As input, a config file containing list sensors described by:
* name of sensor,
* timeout (expressing expected update period, for example every 1h),
* gsn session address (e.g. www.server.com:22001/gsn)
* and possibly a username and password, in case session requires authentication.
* Syntax for configuration file
* # Servers without authentication
* sensor1@10d@www.server.com:22001/gsn
* sensor2@20m@www.server2.com:22003/gsn
* # Server with authentication
* sensor3@1h@www.server4.com:22005/gsn@httpusername:httppassword
* */
public class GSNMonitor {
public static final String CONFIG_SEPARATOR = "@";
public static final String DEFAULT_GSN_LOG4J_PROPERTIES = "conf/log4j.properties";
public static final int STATUS_OK = 0;
public static final int STATUS_WARNING = 1;
public static final int STATUS_CRITICAL = 2;
public static final int STATUS_UNKNOWN = 3;
private static int status = STATUS_OK;
private static transient final Logger logger = Logger.getLogger(VSMonitor.class);
public static HashMap<String, VSensorMonitorConfig> monitoredSensors = new HashMap<String, VSensorMonitorConfig>();
public static HashMap<String, Long> sensorsUpdateDelay = new HashMap<String, Long>();
public static List<GSNSessionAddress> listOfGSNSessions = new Vector<GSNSessionAddress>();
public static List<String> listOfMails = new Vector<String>();
public static StringBuilder errorsBuffer = new StringBuilder();
public static StringBuilder warningsBuffer = new StringBuilder();
public static StringBuilder infosBuffer = new StringBuilder();
public static StringBuilder summary = new StringBuilder();
public static StringBuilder report = new StringBuilder();
private static final String GSN_REALM = "GSNRealm";
private static String gmail_username;
private static String gmail_password;
private static final String SMTP_GMAIL_COM = "smtp.gmail.com";
private static int nHostsDown = 0;
private static int nSensorsLate = 0;
/*
* Reads config file and initializes:
* e-mail config (gmail_username, gmail_password)
* lists
* - monitoredSensors: list of monitored sensors (including expected update delay)
* - sensorsUpdateDelay: list of sensors update delays (initialized to -1)
* - listOfGSNSessions: list of GSN sessions
* */
public static void initFromFile(String fileName) {
long timeout;
String vsensorname;
String host;
String path;
int port;
boolean needspassword;
String password;
String username;
logger.warn("Trying to initialize VSMonitor from file <" + fileName + ">");
try {
BufferedReader in = new BufferedReader(new FileReader(fileName));
String str;
while ((str = in.readLine()) != null) {
// ignore comments starting with #
if (str.trim().indexOf("#") == 0) {
continue;
}
if (str.trim().indexOf("@gmail-username") >= 0) {
gmail_username = str.trim().split(" ")[1];
//System.out.println("GMAIL Username: "+ gmail_username);
continue;
}
if (str.trim().indexOf("@gmail-password") >= 0) {
gmail_password = str.trim().split(" ")[1];
//System.out.println("GMAIL password: "+ gmail_password);
continue;
}
//@gmail-username
//@gmail-password
String[] s = str.trim().split(CONFIG_SEPARATOR);
if (s.length < 3) {
logger.warn("Malformed monitoring line in file <" + fileName + "> : " + str);
//System.out.println("Malformed monitoring line in file <"+ fileName + "> : "+str);
} else {
//System.out.println(s.length+" Elements found");
vsensorname = s[0].trim();
timeout = VSensorMonitorConfig.timeOutFromString(s[1].trim());
//System.out.println("\""+s[2]+"\"");
String[] host_port_path = s[2].split(":");
//System.out.println(host_port_path.length);
if (host_port_path.length != 2) {
logger.warn("Malformed monitoring line in file <" + fileName + "> : " + str);
//System.out.println("Malformed monitoring line in file <"+ fileName + "> : "+str);
continue;
} else {
//System.out.println("["+host_port_path[0].trim()+"]["+host_port_path[1].trim()+"]");
host = host_port_path[0].trim();
int j = host_port_path[1].trim().indexOf("/");
String portStr = host_port_path[1].trim().substring(0, j);
//System.out.println("Port:"+portStr);
path = host_port_path[1].trim().substring(j);
//System.out.println("Path:"+"\""+path+"\"");
try {
port = Integer.parseInt(portStr);
//System.out.println(">>"+port);
} catch (NumberFormatException e) {
logger.warn("Malformed monitoring line in file <" + fileName + "> : " + str);
//System.out.println("Malformed monitoring line in file <"+ fileName + "> : "+str);
continue;
}
if (s.length > 3) { // needs password
needspassword = true;
String[] username_password = s[3].split(":");
if (username_password.length > 1) {
username = username_password[0].trim();
password = username_password[1].trim();
} else {
logger.warn("Malformed monitoring line in file <" + fileName + "> : " + str);
//System.out.println("Malformed monitoring line in file <"+ fileName + "> : "+str);
continue;
}
} else {
needspassword = false;
username = "";
password = "";
}
// DEBUG INFO
//System.out.println("TIMEOUT: "+timeout);
//System.out.println("Creating object with : "+vsensorname+" "+host+" "+port+" "+timeout+" "+path+" "+needspassword+" "+username+" "+password);
monitoredSensors.put(vsensorname, new VSensorMonitorConfig(vsensorname, host, port, timeout, path, needspassword, username, password));
// DEBUG INFO
//System.out.println("RESULT: "+ monitoredSensors.get(vsensorname).toString());
sensorsUpdateDelay.put(vsensorname, new Long(-1)); // not yes initialized, to be initialized when web server is queried
GSNSessionAddress gsnSessionAdress = new GSNSessionAddress(host, path, port, needspassword, username, password); //TODO: insitialize it
if (!listOfGSNSessions.contains(gsnSessionAdress)) {
listOfGSNSessions.add(gsnSessionAdress);
}
//System.out.println("VS: "+"\""+vsensorname+"\""+" timeout: " + Long.toString(timeout));
//System.out.println("Added: "+ monitoredSensors.get(vsensorname));
logger.warn("Added:" + monitoredSensors.get(vsensorname));
}
}
}
} catch (IOException e) {
logger.warn("IO Exception while trying to open file <" + fileName + "> " + e);
}
}
/*
* Queries a GSN session for status
* Uses Http Get method to read xml status (usually under /gsn)
* and initializes global variables :
* - errorsBuffer
* - noErrrorsBuffer
* - nHostsDown
* */
public static void readStatus(GSNSessionAddress gsnSessionAddress) throws Exception {
String httpAddress = gsnSessionAddress.getURL();
DefaultHttpClient client = new DefaultHttpClient();
if (gsnSessionAddress.needsPassword()) {
client.getCredentialsProvider().setCredentials(
new AuthScope(gsnSessionAddress.getHost(), gsnSessionAddress.getPort()/*, GSN_REALM*/),
new UsernamePasswordCredentials(gsnSessionAddress.getUsername(), gsnSessionAddress.getPassword())
);
}
logger.warn("Querying server: " + httpAddress);
HttpGet get = new HttpGet(httpAddress);
try {
// execute the GET, getting string directly
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String responseBody = client.execute(get, responseHandler);
parseXML(responseBody);
} catch (HttpResponseException e) {
errorsBuffer.append("HTTP 401 Authentication Needed for : ")
.append(httpAddress)
.append("\n");
} catch (UnknownHostException e) {
errorsBuffer.append("Unknown host: ")
.append(httpAddress)
.append("\n");
nHostsDown++;
} catch (ConnectException e) {
errorsBuffer.append("Connection refused to host: ")
.append(httpAddress)
.append("\n");
raiseStatusTo(STATUS_CRITICAL);
nHostsDown++;
} finally {
// release any connection resources used by the method
client.getConnectionManager().shutdown();
}
}
public static int raiseStatusTo(int newStatus) {
if (status<newStatus)
status = newStatus;
return status;
}
/*
* Checks update times
* */
public static void checkUpdateTimes() {
for (int i = 0; i < sensorsUpdateDelay.size(); i++) {
Long lastUpdated = (Long) sensorsUpdateDelay.values().toArray()[i];
String sensorName = (String) sensorsUpdateDelay.keySet().toArray()[i];
if (lastUpdated.longValue() > monitoredSensors.get(sensorName).getTimeout()) {
raiseStatusTo(STATUS_WARNING);
warningsBuffer.append(sensorName)
.append("@")
.append(monitoredSensors.get(sensorName).getHost())
.append(":")
.append(monitoredSensors.get(sensorName).getPort())
.append(" not updated for ")
.append(VSensorMonitorConfig.ms2dhms(sensorsUpdateDelay.get(sensorName)))
.append(" (expected <")
.append(VSensorMonitorConfig.ms2dhms(monitoredSensors.get(sensorName).getTimeout()))
.append(")\n");
nSensorsLate++;
} else {
infosBuffer.append(sensorName).append("@")
.append(monitoredSensors.get(sensorName).getHost())
.append(":")
.append(monitoredSensors.get(sensorName).getPort())
.append(" (on time)\n");
}
}
}
/*
* Sends an e-mail to recipients specified in command line
* (through global listOfMails)
* with the global summary as subject
* and global errorsBuffer as body
* */
private static void sendMail() throws EmailException {
SimpleEmail email = new SimpleEmail();
//email.setDebug(true);
email.setHostName(SMTP_GMAIL_COM);
email.setAuthentication(gmail_username, gmail_password);
//System.out.println(gmail_username +" "+ gmail_password);
email.getMailSession().getProperties().put("mail.smtp.starttls.enable", "true");
email.getMailSession().getProperties().put("mail.smtp.auth", "true");
email.getMailSession().getProperties().put("mail.debug", "true");
email.getMailSession().getProperties().put("mail.smtp.port", "465");
email.getMailSession().getProperties().put("mail.smtp.socketFactory.port", "465");
email.getMailSession().getProperties().put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
email.getMailSession().getProperties().put("mail.smtp.socketFactory.fallback", "false");
email.getMailSession().getProperties().put("mail.smtp.starttls.enable", "true");
for (String s : listOfMails) {
email.addTo(s);
}
email.setFrom(gmail_username + "@gmail.com", gmail_username);
email.setSubject("[GSN Alert] " + summary.toString());
email.setMsg(report.toString());
email.send();
}
/*
* parses the XML string
* and initializes sensorsUpdateDelay
* with update delays for relevant sensors
* */
public static void parseXML(String s) {
try {
DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputSource inputSource = new InputSource();
inputSource.setCharacterStream(new StringReader(s));
Document document = documentBuilder.parse(inputSource);
NodeList nodes = document.getElementsByTagName("virtual-sensor");
for (int i = 0; i < nodes.getLength(); i++) {
Element element = (Element) nodes.item(i);
String sensor_name = element.getAttribute("name");
if (!sensorsUpdateDelay.containsKey(sensor_name))
continue; // skip sensors that are not monitored
logger.warn("Sensor: " + sensor_name);
NodeList listOfField = element.getElementsByTagName("field");
for (int j = 0; j < listOfField.getLength(); j++) {
Element line = (Element) listOfField.item(j);
if (line.getAttribute("name").indexOf("timed") >= 0) {
String last_updated_as_string = line.getTextContent();
try {
Long last_updated_as_Long = GregorianCalendar.getInstance().getTimeInMillis() - VSensorMonitorConfig.datetime2timestamp(last_updated_as_string);
logger.warn(new StringBuilder(last_updated_as_string)
.append(" => ")
.append(VSensorMonitorConfig.ms2dhms(last_updated_as_Long))
.toString());
sensorsUpdateDelay.put(sensor_name, last_updated_as_Long);
} catch (ParseException e) {
errorsBuffer.append("Last update time for sensor ")
.append(sensor_name)
.append(" cannot be read. Error while parsing > ")
.append(last_updated_as_string)
.append(" <\n");
}
}
}
}
} catch (Exception e) {
logger.warn("Exception while parsing XML\n");
e.printStackTrace();
}
}
/*
* Prints the stack trace of the exception to a string.
* */
public static String getStackTrace(Throwable t) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter, true);
t.printStackTrace(printWriter);
printWriter.flush();
stringWriter.flush();
return stringWriter.toString();
}
public static void main(String[] args) {
PropertyConfigurator.configure(DEFAULT_GSN_LOG4J_PROPERTIES);
String configFileName;
if (args.length >= 2) {
configFileName = args[0];
System.out.println("Using config file: " + configFileName);
for (int i = 1; i < args.length; i++) {
System.out.println("Adding e-mail: " + args[i]);
listOfMails.add(args[i]);
}
} else {
System.out.println("Usage java -jar VSMonitor.jar <config_file> <list_of_mails>");
System.out.println("e.g. java -jar VSMonitor.jar conf/monitoring.cfg user@gmail.com admin@gmail.com");
return;
}
initFromFile(configFileName);
// for each monitored GSN server
Iterator iter = listOfGSNSessions.iterator();
while (iter.hasNext()) {
try {
readStatus((GSNSessionAddress) iter.next());
} catch (Exception e) {
logger.error("Exception: " + e.getMessage());
logger.error("StackTrace:\n" + getStackTrace(e));
}
}
checkUpdateTimes();
// Generate Report
report.append("\n[ERROR]\n" + errorsBuffer)
.append("\n[WARNING]\n" + warningsBuffer)
.append("\n[INFO]\n" + infosBuffer);
if ((nSensorsLate > 0) || (nHostsDown > 0)) {
summary.append("WARNING: ");
if (nHostsDown > 0)
summary.append(nHostsDown + " host(s) down. ");
if (nSensorsLate > 0)
summary.append(nSensorsLate + " sensor(s) not updated. ");
// Send e-mail only if there are errors
try {
sendMail();
} catch (EmailException e) {
logger.error("Cannot send e-mail. " + e.getMessage());
logger.error("StackTrace:\n" + getStackTrace(e));
}
}
// Showing report
System.out.println(summary);
System.out.println(report);
System.exit(status);
}
}