#!/usr/bin/env python
# -*- Mode: python -*-
#
# Usage: nodeinfod [-d] port
#
# A "sensor" server derived from slicestat_httpd by Brent Chun (bnc).
#
# Created: 16 July 2003
# Derived: 28 April 2005
#
# $Id: nodeinfod,v 1.2 2005-05-03 10:53:30-04 sit Exp $
#
import os
import re
import sys
import time
import threading
import SimpleHTTPServer
import SocketServer

def getlocalip():
    """Try TCP connections to DNS servers; use gethostbyname as last resort"""
    import socket
    hostname = socket.gethostname()
    ips = socket.gethostbyname_ex(hostname)[2]
    for ip in ips:
        if ip != "127.0.0.1":
            return ip
    for host in [ "www.yahoo.com", "www.microsoft.com", "www.aol.com",
                  "www.google.com", "www.amazon.com" ]:
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((host, 80))
            localaddr, localport = s.getsockname()
            s.close()
            return localaddr
        except:
            continue
    host = socket.getfqdn(socket.gethostname())
    return socket.gethostbyname(host)

class InvalidSensorException(Exception):
    def __init__(self, msg):
        self.msg = msg

class SensorHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def check_access (self, ip):
	if (ip == '127.0.0.1' or ip[:3] == "18." or ip[:7] == "128.30." or ip[:7] == "128.31."):
	    return True
	else:
	    try:
		return self.headers['Referer'].index ("http://tears.lcs.mit.edu/cgi-bin/summarize.py") == 0
	    except:
		pass
	return False

    def do_HEAD(self):
        ip = self.client_address[0]
	if not self.check_access (ip):
            error404 = "Remote access disallowed\n"
            self.send_head(404, len(error404), "text/plain")            
            return 
        try:
            body = self.server.read_sensor(self.path)
            self.send_head(200, len(body), "text/plain")
        except InvalidSensorException, e:
            error404 = "Invalid sensor.  See /README\n"
            self.send_head(404, len(error404), "text/plain")
        except: pass
            
    def do_GET(self):
        ip = self.client_address[0]
	if not self.check_access (ip):
            error404 = "Remote access disallowed\n"
            self.send_head(404, len(error404), "text/plain")
            self.wfile.write(error404)            
            return 
        try:
            body = self.server.read_sensor(self.path, self.headers)
            self.send_head(200, len(body), "text/plain")
            self.wfile.write(body)
        except InvalidSensorException, e:
            error404 = e.msg
            self.send_head(404, len(error404), "text/plain")
            self.wfile.write(error404)
        except: pass
        
    def send_head(self, status, length, type):
        import calendar, time
        date = time.strftime("%a %b %d %H:%M:%S %Y", time.gmtime())
        self.send_response(status)
        self.send_header("Content-length", length)
        self.send_header("Content-type", type)
        self.send_header("Date", date)
        self.end_headers()

class SensorServer(SocketServer.ThreadingTCPServer):
    def __init__(self, server_address, RequestHandlerClass):
        self.port = server_address[1]
        self.sensors = {}
        SocketServer.ThreadingTCPServer.allow_reuse_address = 1
        try:
            method = SocketServer.ThreadingTCPServer.__init__
            args = [ self, server_address, RequestHandlerClass ]
            retryapply(method, args, 10, 1)
        except:
            raise "Could not bind to TCP port %d" % server_address[1]

    def add_sensor(self, sensor):
        self.sensors[sensor.name()] = sensor

    def read_sensor(self, path, headers):
        import re
        m = re.match("/([^/]+)", path) # Get sensor name
        if path == "/":
            data = ""
            for name in self.sensors:
                data = data + self.sensors[name].description() + "\n"
            return data
        elif path == "/README":
            return "Simple sensor server.\n"
        elif m:
            name = m.group(1)
            if name in self.sensors:
                return self.sensors[name].read(path, headers)
            else:
                raise InvalidSensorException("Invalid sensor.  See /README\n")
        else:
            raise InvalidSensorException("Invalid sensor.  See /README\n")

class SliceStatSensor:
    def __init__(self):
        self.ip = getlocalip()

    def name(self):
        return "slicestat"

    def description(self):
        return "/slicestat Version mit_dht (slice resource statistics)" 

    def read(self, path, headers):
	if path == "/slicestat/df":
	    return self.do_cmd ("df")
        elif path == "/slicestat/ps":
            return self.do_cmd ("ps fauxww")
        else:
            error404 = "Invalid sensor.\n"
            return error404

    def do_cmd (self, cmd):
        fin = os.popen(cmd, "r")
        try:
            lines = fin.readlines()
        except:
            fin.close()
            raise
        fin.close()
        return "".join(lines)
    
def retryapply(method, args, maxtries, sleeptime):
    """Retry idempotent method up to maxtries times until it succeeds"""
    import time
    tries = 0
    for i in range(maxtries):
        try:
            rval = apply(method, args)
        except:
            tries += 1
            time.sleep(sleeptime)
        else:
            break
    if tries == maxtries:
        raise "Could not apply method in %d tries" % maxtries
    return rval

def daemonize():
    import os, sys
    pid = os.fork()
    if pid > 0:
        sys.exit(0)
    os.setsid()
    os.close(0)
    os.close(1)
    os.close(2)
    sys.stdin = open("/dev/null")
    sys.stdout = open("/dev/null", "w")
    sys.stderr = open("/dev/null", "w")
    pid = os.fork()
    if pid > 0:
        sys.exit(0)
#    open("/var/run/slicestat.pid", "w").write("%d\n" % os.getpid())

def parsecmdline(argv):
    import getopt
    shortopts = "d"
    longopts = [ "debug" ]
    flags = { "debug" : None  }
    opts, args = getopt.getopt(argv[1:], shortopts, longopts)
    for o, v in opts:
        if o in ("-d", "--debug"):
            flags["debug"] = 1
    return args, flags

if __name__ == "__main__":
    import os, pwd
    args, flags = parsecmdline(sys.argv)
    if len(args) != 1:
        print "Usage: slicestat_httpd port"
        sys.exit(3)
    if not flags["debug"]:
        daemonize()
    httpd = SensorServer(("", int(args[0])), SensorHandler)
    httpd.add_sensor(SliceStatSensor())
    httpd.serve_forever()
