#!/usr/bin/env python
# $Id: hbc,v 1.9 2012/03/29 02:08:36 andreas Exp $
import sys
import time
import socket
import os
import signal
import getopt
import string
import select
import errno

PORT=50003
INTERVAL=10
DBG = True

sock=None

class NullDevice:
    def write(self, s):
        pass



class Flock:
	def __init__(self, lock_file):
		self.lock_file = lock_file
		self.fd = None
		self.opid = None

	def lock(self):
		if DBG: print "lock it"
		while 1:
			self.fd = None
			try:
				self.fd = os.open(self.lock_file, os.O_CREAT | os.O_EXCL | os.O_RDWR)
			except OSError, e:
				if e.errno != errno.EEXIST:
					raise
				if DBG: print "create error %s" % e.errno
			except:
				raise
			if DBG: print "lock() - self.fd = '%s'" % self.fd	
			if not self.fd:
				if self.oproc():
					if DBG: print "process is alive"
					os.remove(self.lock_file)
					continue
				else:
					if DBG: print "no pid process??"
	
			if self.fd:
				f=os.fdopen(self.fd, 'w').write("%s" % os.getpid())
			return self.fd
	
	
	def unlock(self):
		os.remove(self.lock_file)
		self.fd=None
		

	def setopid(self):
		try:
			self.opid=open(self.lock_file).readline()
			if DBG: print "setopid = '%s'" % self.opid
			if self.opid == '':
				os.remove(self.lock_file)
		except:
			self.opid = ''
		if self.opid == '':
			self.opid = None


	def oproc(self):
		self.setopid()
		if not self.opid:
			return False
		try:
			os.kill(int(self.opid), 0)
			return True
		except:
			pass
		return False

def handler(signum, frame):
	global up
	if up == 0:
		return
	sys.exit(0)


def getsock(host):
	try:
		r=socket.getaddrinfo(host,  50001, 0, 0, socket.SOL_UDP)
	except socket.gaierror:
		return None
	if r[0][0] == 28:
		af_type=socket.AF_INET6
	elif r[0][0] == 2:
		af_type=socket.AF_INET
	else:
		return None
	if verbose:
		print "socktype: %s" % af_type
	sock=socket.socket(af_type, socket.SOCK_DGRAM)
	sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, \
            sock.getsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR) | 1)
	if verbose: print "get socket %s" % sock
	
	return sock

def socksend(msg, tohost):
	global sock

	if sock == None:
		sock=getsock(tohost[0])
	sock.sendto(msg, tohost)
	if verbose: print "msg %s sent" % msg


msgonly=False
helpflag=False
verbose=False
daemon=False
optlist=[]  
args=[]
msgboot=[]
home=os.environ['HOME']
configfile="%s/.hbrc" % home

try:
    optlist, args = getopt.getopt(sys.argv[1:], 'bc:dhm:v')
except:
    helpflag=True

for o,a in optlist:
	if o == '-b':
		msgboot.append("boot=1")
	elif o == '-c':
		configfile=a
	elif o == '-d':
		daemon=True
	elif o == '-h':
		helpflag=True
	elif o == '-m':
		msgboot.append("service=%s" % "service")
		a.replace(';',':')
		msgboot.append("msg=%s" % a)
		msgonly=True
	elif o == '-v':
		verbose=True


if helpflag:
	print "hbc HeartBeatClient"
	print "usage: hbc [-bdhv] [-c configfile] [-m msg][host1 [..]]"
	print
	print "	-b	indicate machine boot"
	print "	-c configfile"
	print "	-d daemonize"
	print "	-h this help"
	print "	-m send a message"
	print "	-v verbose"
	print
	print """ config file can contain 
hb_hosts=('host1', 'host2', ..._
hb_port=50003
interval=20
logfile=...
logfmt={|test|msg}
grace=SECONDS
reportstrict={True|False}
"""

	sys.exit(1)

#
# set defaults

hb_port=PORT
interval=INTERVAL
hb_hosts=[]
iam=socket.gethostname()

try:
	f=open(configfile,"r")	
	if verbose: print "notice: using config file %s" % configfile
except:
	if verbose: print "warning: running without config file: %s" % configfile
	f=None

if f:
	while 1:
		l=f.readline()
		if len(l) == 0:
			break
		r=l[:-1].split('=')
		if r[0] == 'hb_hosts':
			hb_hosts=eval(r[1])
			if verbose:
				print "notice:  cfg hb_hosts: %s" % hb_hosts
		elif r[0] == 'interval':
			interval=eval(r[1])
		elif r[0] == 'hb_port':
			hb_port=eval(r[1])
		elif r[0] == 'name':
			iam=eval(r[1])
			if verbose: print "name set to %s" % iam
	f.close()

if len(args) != 0:
	hb_hosts=args

if len(hb_hosts) == 0:
	print "no hb server specified"
	sys.exit(1)



if verbose:
	print "notice: hb_hosts: %s" % str(hb_hosts)
	print "notice: hb_port: %s" % hb_port
	print "notice: interval: %s" % interval
	print "notice: iam: %s" % iam

if not msgonly:
	msgboot.append("interval=%s" % interval)

if len(msgboot) > 0:
	msgboot.append("name=%s" % iam)
	msgboot.append("time=%s" % time.time())
	msgboot.append("acks=0")
	msg=";".join(msgboot)
	while 1:
		fail=0
		for hb_host in hb_hosts:
			try:
				if verbose: print "sock.send('%s', (%s, %s))" % (msg, hb_host, hb_port)
				socksend(msg, (hb_host, hb_port))
			except:
				fail=1
		if fail:
			time.sleep(10)
		else:
			break
	
if verbose: print "msgboot done msgonly=%s" % msgonly
if msgonly:
		sys.exit(0)


#
# 
if daemon:
	pid=os.fork()
	if pid > 0:
		if verbose:
			print "daemoinizing... pid=%d" % pid
		sys.exit(0)


	os.close(0)
	os.close(1)
	os.close(2)
	sys.stdin.close()
	sys.stdout = NullDevice()
	sys.stderr = NullDevice()
	os.chdir("/")
	os.setsid() 
	os.umask(0) 


while True:
	lock=Flock('/tmp/hbc.pid')
	if DBG: print "get lock"
	if lock.lock():
		if DBG: print "got lock"
		break
	if not lock.oproc():
		sys.exit(1)
	os.kill(lock.opid,15)
	time.sleep(1)

up=1
signal.signal(signal.SIGTERM, handler)
signal.signal(signal.SIGHUP, handler)
ackcount=0
lastT=time.time()
while up:
	sleep=(lastT+interval) - time.time() 
	if verbose: print "sleep %s" % sleep
	if sleep > 0:
		try:
			r=select.select([sock.fileno()],[],[],sleep)
#			time.sleep(interval)
		except:
			break
		if verbose: print r
		if sock.fileno() in r[0]:
			data, addr = sock.recvfrom(1024)
			if  data == "ACK":
				ackcount+=1
			else:
				try:
					os.system(data)
				except:
					pass
			continue	
	lastT=time.time()
	for hb_host in hb_hosts:
		try:
			msg="interval=%s;name=%s;time=%s;acks=%s" % (interval, iam, time.time(), ackcount)
			if verbose: print "sock.send('%s', (%s, %s))" % (msg, hb_host, hb_port)
			socksend(msg, (hb_host, hb_port))
		except:
			pass

up=0
msg="shutdown=1;name=%s;acks=%s" % (iam, ackcount)
for hb_host in hb_hosts:
	if verbose: print "hbc: sock.send('%s', (%s, %s))" % (msg, hb_host, hb_port)
	socksend(msg, (hb_host, hb_port))
time.sleep(1)
sock.close()
lock.unlock()
