Index: trunk/server/common/oursrc/hacron/hacron
===================================================================
--- trunk/server/common/oursrc/hacron/hacron	(revision 1456)
+++ trunk/server/common/oursrc/hacron/hacron	(revision 1456)
@@ -0,0 +1,329 @@
+#!/usr/bin/env python
+from __future__ import with_statement
+
+import glob
+import logging.handlers
+import optparse
+import os
+import socket
+import shutil
+import subprocess
+import sys
+import time
+from os import path
+
+OCF_SUCCESS=0
+OCF_ERR_GENERIC=1
+OCF_ERR_ARGS=2
+OCF_ERR_UNIMPLEMENTED=3
+OCF_ERR_PERM=4
+OCF_ERR_INSTALLED=5
+OCF_ERR_CONFIGURED=6
+OCF_NOT_RUNNING=7
+
+logger = logging.getLogger('cron')
+
+import os
+import subprocess
+
+HA_LOGD = os.environ.get('HA_LOGD') == 'xyes'
+
+class HaLogHandler(logging.Handler):
+    """
+    A handler class which writes to ha_logger.
+    """
+    def __init__(self, ha_tag):
+        """
+        Initialize the handler.  ha_tag is the name of this resource.
+        """
+        logging.Handler.__init__(self)
+        self.ha_tag = ha_tag
+
+    def emit(self, record):
+        """
+        Emit a record.
+        """
+        print 'Passed', record
+        try:
+            levelname = record.levelname
+            msg = self.format(record)
+            subprocess.call(['/usr/sbin/ha_logger', '-t', self.ha_tag, msg])
+        except (KeyboardInterrupt, SystemExit):
+            raise
+        except:
+            self.handleError(record)
+
+class lock(object):
+    def __init__(self, name):
+        self.name = name
+
+    def __enter__(self):
+        while True:
+            try:
+                self.lock = os.open(self.name, os.O_RDWR | os.O_CREAT | os.O_EXCL)
+            except OSError:
+                logger.error('Could not acquire lock %s.  Sleeping...' % self.name)
+                time.sleep(0.5)
+            else:
+                break
+            
+    def __exit__(self, type, value, traceback):
+        os.close(self.lock)
+        _remove(self.name)
+
+def _touch(path):
+    """Effectively touches a file.  Returns true if successful, false
+    otherwise"""
+    try:
+        open(path, 'a').close()
+    except IOError:
+        return False
+    else:
+        return True
+
+def _remove(dest):
+    try:
+        if path.isdir(dest):
+            os.rmdir(dest)
+        else:
+            os.remove(dest)
+    except OSError, e:
+        logging.error('Could not remove %s: %s' % (dest, e))
+        return False
+    else:
+        return True
+
+def _mkdir(dir):
+    try:
+        os.mkdir(dir)
+    except OSError, e:
+        logging.error('Could not mkdir %s: %s' % (dir, e))
+        return False
+    else:
+        return True
+    
+def _strip(name):
+    """Strip off the file extension, and leading /'s, if they exist"""
+    return path.splitext(path.basename(name))[0]
+
+def _suffix(name, suffix):
+    return '%s.%s' % (name, suffix)
+
+def _crondir(server):
+    return path.join(CRONSPOOL_DIR, _suffix(server, 'cronspool'))
+
+def _server_exists(server):
+    return path.exists(path.join(SERVER_DIR, server))
+
+def _serverfile(server):
+    return path.join(SERVER_DIR, server)
+
+def _servers():
+    """Get a list of the servers."""
+    return [_strip(f) for f in glob.glob(path.join(SERVER_DIR, '*'))]
+
+def _is_master(server):
+    crondir = path.join(CRONSPOOL_DIR, _suffix(server, 'cronspool'))
+    return path.islink(crondir)
+
+def start_cron(args, options):
+    if not _touch(_serverfile(HOSTNAME)):
+        return OCF_ERR_CONFIGURED
+
+    logger.info('Starting %s' % HOSTNAME)
+    if _is_master(HOSTNAME):
+        logger.error('%s is already the master!' % HOSTNAME)
+    for server in _servers():
+        crondir = _crondir(server)
+        if server == HOSTNAME:
+            _remove(crondir)
+            os.symlink('../cronspool', crondir)
+            logger.info('Created master symlink %s' % crondir)
+        else:
+            if path.islink(crondir):
+                _remove(crondir)
+                logger.info('Removed old master symlink: %s' % crondir)
+            if not path.exists(crondir):
+                _mkdir(crondir)
+                logger.info('Created slave dummy directory %s' % crondir)
+
+    if CRON_RESTART_COMMAND:
+        ret = subprocess.call(CRON_RESTART_COMMAND)
+        if ret:
+            logger.error('Cron restart exited with return code %d' % ret)
+            return OCF_ERR_GENERIC
+        else:
+            logger.info('Restarted crond')
+    return OCF_SUCCESS
+
+def stop_cron(args, options):
+    if not _is_master(HOSTNAME):
+        logger.error('I am not the master!')
+    else:
+        crondir = _crondir(HOSTNAME)
+        logger.info('Removing symlink %s' % crondir)
+        _remove(crondir)
+        _mkdir(crondir)
+    return OCF_SUCCESS
+
+def monitor_cron(args, options):
+    if _is_master(HOSTNAME):
+        return OCF_SUCCESS
+    else:
+        return OCF_NOT_RUNNING
+
+def validate_all_cron(args, options):
+    if not _touch(_serverfile(HOSTNAME)):
+        logger.error('Could not touch %s' % _serverfile(HOSTNAME))
+        return OCF_GENERIC_ERR
+    if not path.exists(CRONSPOOL_DIR):
+        return OCF_GENERIC_ERR
+
+def setup(args, options):
+    for d in [CRONSPOOL_DIR, SERVER_DIR]:
+        if not path.exists(d):
+            os.makedirs(d)
+            logger.info('Created %s' % d)
+        else:
+            logger.info('Already exists: %s' % d)
+
+def add_servers(servers, options):
+    for server in servers:
+        _touch(_serverfile(server))
+
+def remove_servers(servers, options):
+    for server in servers:
+        os.unlink(_serverfile(server))
+
+def meta_data_cron(args, options):
+    print """<?xml version="1.0"?>
+<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
+<resource-agent name="hacron" version="0.1">
+<version>1.0</version>
+
+<longdesc lang="en">
+This is the high-availability cron manager.  It uses an extremely overpowered
+clustering solution to make it so that people can have their crontabs.  Yay.
+</longdesc>
+<shortdesc lang="en">HA Cron</shortdesc>
+
+<parameters>
+<parameter name="cron_root" required="1">
+<longdesc lang="en">
+Base directory for storage of crontabs and server information.
+</longdesc>
+<shortdesc lang="en">Cron base directory</shortdesc>
+<content type="string" />
+</parameter>
+
+<parameter name="cron_restart_cmd">
+<longdesc lang="en">
+Command to restart cron.
+</longdesc>
+<shortdesc lang="en">Restart cron cmd</shortdesc>
+<content type="string" />
+</parameter>
+</parameters>
+
+<actions>
+<action name="start"        timeout="90" />
+<action name="stop"         timeout="100" />
+<action name="monitor"      timeout="20" interval="10" depth="0" start-delay="0" />
+<action name="reload"       timeout="90" />
+<action name="meta-data"    timeout="5" />
+<action name="validate-all"   timeout="30" />
+</actions>
+</resource-agent>
+"""
+    return OCF_SUCCESS
+
+def usage(parser):
+    parser.print_help()
+    return 1
+
+def _set_globals(args, options):
+    global HOSTNAME, CRONROOT, CRONSPOOL_DIR, SERVER_DIR, CRON_RESTART_COMMAND, \
+        HA_RSCTMP, OCF_RESOURCE_INSTANCE
+    if options.development:
+        logging.basicConfig(level=logging.DEBUG)
+    else:
+        if HA_LOGD:
+            handler = HaLogHandler('hacron')
+        else:
+            handler = logging.handlers.SysLogHandler('/dev/log')
+        formatter = logging.Formatter("%(module)s: %(levelname)s %(message)s")
+        handler.setLevel(logging.INFO)
+        handler.setFormatter(formatter)
+        logger.addHandler(handler)
+    HOSTNAME = options.server or os.environ.get('HA_CURHOST') or socket.gethostname()
+    CRONROOT = options.cronroot or os.environ.get('OCF_RESKEY_cron_root')
+    if not CRONROOT:
+        logging.error('No cron_root specified.')
+        return OCF_ERR_CONFIGURED
+    CRONSPOOL_DIR = path.join(CRONROOT, 'server-cronspools')
+    SERVER_DIR = path.join(CRONROOT, 'servers')
+    CRON_RESTART_COMMAND = options.cron_restart or os.environ.get('OCF_RESKEY_cron_restart_cmd')
+
+    HA_RSCTMP = os.environ.get('HA_RSCTMP', '/tmp')
+    OCF_RESOURCE_INSTANCE = os.environ.get('OCF_RESOURCE_INSTANCE', 'default')
+    return OCF_SUCCESS
+
+def main():
+    cmds = ['start', 'reload', 'stop', 'monitor', 'validate-all', 'setup',
+            'remove-servers', 'meta-data']
+    usage_str = "usage: %%prog [%s]" % '|'.join(cmds)
+    parser = optparse.OptionParser(usage=usage_str)
+    parser.add_option("-s", "--server",
+                      action="store", dest="server",
+                      default=None,
+                      help="choose which server to run script as")
+    parser.add_option("-c", "--cronroot",
+                      action="store", dest="cronroot",
+                      default=None,
+                      help="pick root of cron dir")
+    parser.add_option("-d", "--development",
+                      action="store_true", dest="development",
+                      default=False,
+                      help="run in production")
+    parser.add_option("-r", "--cron-restart",
+                      action="store", dest="cron_restart",
+                      default=None,
+                      help="run in production")
+    (options, args) = parser.parse_args()
+    if len(args) < 1:
+        return usage(parser)
+    command = args[0]
+    args = args[1:]
+
+    if command == 'meta-data':
+        return meta_data_cron(args, options)
+    globals_status = _set_globals(args, options)
+    with lock('%s/hacron-%s.lock' % (HA_RSCTMP, OCF_RESOURCE_INSTANCE)):
+        if globals_status:
+            return globals_status
+        if command == 'start':
+            return start_cron(args, options)
+        elif command == 'reload':
+            return start_cron(args, options)
+        elif command == 'stop':
+            return stop_cron(args, options)
+        elif command == 'monitor':
+            return monitor_cron(args, options)
+        elif command == 'validate-all':
+            return validate_all_cron(args, options)
+        elif command == 'setup':
+            return setup(args, options)
+        elif command == 'add-servers':
+            return remove_servers(args, options)
+        else:
+            usage(parser)
+            return OCF_ERR_UNIMPLEMENTED
+
+if __name__ == '__main__':
+    try:
+        ret = main()
+    except Exception, e:
+        logger.error('exception from main: %s' % e)
+        ret = OCF_ERR_GENERIC
+        raise
+    sys.exit(ret)
