#!/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 """ 1.0 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. HA Cron Base directory for storage of crontabs and server information. Cron base directory Command to restart cron. Restart cron cmd """ 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)