source: trunk/server/common/oursrc/hacron/hacron @ 1456

Last change on this file since 1456 was 1456, checked in by gdb, 14 years ago
Added hacron script
  • Property svn:executable set to *
File size: 9.7 KB
Line 
1#!/usr/bin/env python
2from __future__ import with_statement
3
4import glob
5import logging.handlers
6import optparse
7import os
8import socket
9import shutil
10import subprocess
11import sys
12import time
13from os import path
14
15OCF_SUCCESS=0
16OCF_ERR_GENERIC=1
17OCF_ERR_ARGS=2
18OCF_ERR_UNIMPLEMENTED=3
19OCF_ERR_PERM=4
20OCF_ERR_INSTALLED=5
21OCF_ERR_CONFIGURED=6
22OCF_NOT_RUNNING=7
23
24logger = logging.getLogger('cron')
25
26import os
27import subprocess
28
29HA_LOGD = os.environ.get('HA_LOGD') == 'xyes'
30
31class HaLogHandler(logging.Handler):
32    """
33    A handler class which writes to ha_logger.
34    """
35    def __init__(self, ha_tag):
36        """
37        Initialize the handler.  ha_tag is the name of this resource.
38        """
39        logging.Handler.__init__(self)
40        self.ha_tag = ha_tag
41
42    def emit(self, record):
43        """
44        Emit a record.
45        """
46        print 'Passed', record
47        try:
48            levelname = record.levelname
49            msg = self.format(record)
50            subprocess.call(['/usr/sbin/ha_logger', '-t', self.ha_tag, msg])
51        except (KeyboardInterrupt, SystemExit):
52            raise
53        except:
54            self.handleError(record)
55
56class lock(object):
57    def __init__(self, name):
58        self.name = name
59
60    def __enter__(self):
61        while True:
62            try:
63                self.lock = os.open(self.name, os.O_RDWR | os.O_CREAT | os.O_EXCL)
64            except OSError:
65                logger.error('Could not acquire lock %s.  Sleeping...' % self.name)
66                time.sleep(0.5)
67            else:
68                break
69           
70    def __exit__(self, type, value, traceback):
71        os.close(self.lock)
72        _remove(self.name)
73
74def _touch(path):
75    """Effectively touches a file.  Returns true if successful, false
76    otherwise"""
77    try:
78        open(path, 'a').close()
79    except IOError:
80        return False
81    else:
82        return True
83
84def _remove(dest):
85    try:
86        if path.isdir(dest):
87            os.rmdir(dest)
88        else:
89            os.remove(dest)
90    except OSError, e:
91        logging.error('Could not remove %s: %s' % (dest, e))
92        return False
93    else:
94        return True
95
96def _mkdir(dir):
97    try:
98        os.mkdir(dir)
99    except OSError, e:
100        logging.error('Could not mkdir %s: %s' % (dir, e))
101        return False
102    else:
103        return True
104   
105def _strip(name):
106    """Strip off the file extension, and leading /'s, if they exist"""
107    return path.splitext(path.basename(name))[0]
108
109def _suffix(name, suffix):
110    return '%s.%s' % (name, suffix)
111
112def _crondir(server):
113    return path.join(CRONSPOOL_DIR, _suffix(server, 'cronspool'))
114
115def _server_exists(server):
116    return path.exists(path.join(SERVER_DIR, server))
117
118def _serverfile(server):
119    return path.join(SERVER_DIR, server)
120
121def _servers():
122    """Get a list of the servers."""
123    return [_strip(f) for f in glob.glob(path.join(SERVER_DIR, '*'))]
124
125def _is_master(server):
126    crondir = path.join(CRONSPOOL_DIR, _suffix(server, 'cronspool'))
127    return path.islink(crondir)
128
129def start_cron(args, options):
130    if not _touch(_serverfile(HOSTNAME)):
131        return OCF_ERR_CONFIGURED
132
133    logger.info('Starting %s' % HOSTNAME)
134    if _is_master(HOSTNAME):
135        logger.error('%s is already the master!' % HOSTNAME)
136    for server in _servers():
137        crondir = _crondir(server)
138        if server == HOSTNAME:
139            _remove(crondir)
140            os.symlink('../cronspool', crondir)
141            logger.info('Created master symlink %s' % crondir)
142        else:
143            if path.islink(crondir):
144                _remove(crondir)
145                logger.info('Removed old master symlink: %s' % crondir)
146            if not path.exists(crondir):
147                _mkdir(crondir)
148                logger.info('Created slave dummy directory %s' % crondir)
149
150    if CRON_RESTART_COMMAND:
151        ret = subprocess.call(CRON_RESTART_COMMAND)
152        if ret:
153            logger.error('Cron restart exited with return code %d' % ret)
154            return OCF_ERR_GENERIC
155        else:
156            logger.info('Restarted crond')
157    return OCF_SUCCESS
158
159def stop_cron(args, options):
160    if not _is_master(HOSTNAME):
161        logger.error('I am not the master!')
162    else:
163        crondir = _crondir(HOSTNAME)
164        logger.info('Removing symlink %s' % crondir)
165        _remove(crondir)
166        _mkdir(crondir)
167    return OCF_SUCCESS
168
169def monitor_cron(args, options):
170    if _is_master(HOSTNAME):
171        return OCF_SUCCESS
172    else:
173        return OCF_NOT_RUNNING
174
175def validate_all_cron(args, options):
176    if not _touch(_serverfile(HOSTNAME)):
177        logger.error('Could not touch %s' % _serverfile(HOSTNAME))
178        return OCF_GENERIC_ERR
179    if not path.exists(CRONSPOOL_DIR):
180        return OCF_GENERIC_ERR
181
182def setup(args, options):
183    for d in [CRONSPOOL_DIR, SERVER_DIR]:
184        if not path.exists(d):
185            os.makedirs(d)
186            logger.info('Created %s' % d)
187        else:
188            logger.info('Already exists: %s' % d)
189
190def add_servers(servers, options):
191    for server in servers:
192        _touch(_serverfile(server))
193
194def remove_servers(servers, options):
195    for server in servers:
196        os.unlink(_serverfile(server))
197
198def meta_data_cron(args, options):
199    print """<?xml version="1.0"?>
200<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
201<resource-agent name="hacron" version="0.1">
202<version>1.0</version>
203
204<longdesc lang="en">
205This is the high-availability cron manager.  It uses an extremely overpowered
206clustering solution to make it so that people can have their crontabs.  Yay.
207</longdesc>
208<shortdesc lang="en">HA Cron</shortdesc>
209
210<parameters>
211<parameter name="cron_root" required="1">
212<longdesc lang="en">
213Base directory for storage of crontabs and server information.
214</longdesc>
215<shortdesc lang="en">Cron base directory</shortdesc>
216<content type="string" />
217</parameter>
218
219<parameter name="cron_restart_cmd">
220<longdesc lang="en">
221Command to restart cron.
222</longdesc>
223<shortdesc lang="en">Restart cron cmd</shortdesc>
224<content type="string" />
225</parameter>
226</parameters>
227
228<actions>
229<action name="start"        timeout="90" />
230<action name="stop"         timeout="100" />
231<action name="monitor"      timeout="20" interval="10" depth="0" start-delay="0" />
232<action name="reload"       timeout="90" />
233<action name="meta-data"    timeout="5" />
234<action name="validate-all"   timeout="30" />
235</actions>
236</resource-agent>
237"""
238    return OCF_SUCCESS
239
240def usage(parser):
241    parser.print_help()
242    return 1
243
244def _set_globals(args, options):
245    global HOSTNAME, CRONROOT, CRONSPOOL_DIR, SERVER_DIR, CRON_RESTART_COMMAND, \
246        HA_RSCTMP, OCF_RESOURCE_INSTANCE
247    if options.development:
248        logging.basicConfig(level=logging.DEBUG)
249    else:
250        if HA_LOGD:
251            handler = HaLogHandler('hacron')
252        else:
253            handler = logging.handlers.SysLogHandler('/dev/log')
254        formatter = logging.Formatter("%(module)s: %(levelname)s %(message)s")
255        handler.setLevel(logging.INFO)
256        handler.setFormatter(formatter)
257        logger.addHandler(handler)
258    HOSTNAME = options.server or os.environ.get('HA_CURHOST') or socket.gethostname()
259    CRONROOT = options.cronroot or os.environ.get('OCF_RESKEY_cron_root')
260    if not CRONROOT:
261        logging.error('No cron_root specified.')
262        return OCF_ERR_CONFIGURED
263    CRONSPOOL_DIR = path.join(CRONROOT, 'server-cronspools')
264    SERVER_DIR = path.join(CRONROOT, 'servers')
265    CRON_RESTART_COMMAND = options.cron_restart or os.environ.get('OCF_RESKEY_cron_restart_cmd')
266
267    HA_RSCTMP = os.environ.get('HA_RSCTMP', '/tmp')
268    OCF_RESOURCE_INSTANCE = os.environ.get('OCF_RESOURCE_INSTANCE', 'default')
269    return OCF_SUCCESS
270
271def main():
272    cmds = ['start', 'reload', 'stop', 'monitor', 'validate-all', 'setup',
273            'remove-servers', 'meta-data']
274    usage_str = "usage: %%prog [%s]" % '|'.join(cmds)
275    parser = optparse.OptionParser(usage=usage_str)
276    parser.add_option("-s", "--server",
277                      action="store", dest="server",
278                      default=None,
279                      help="choose which server to run script as")
280    parser.add_option("-c", "--cronroot",
281                      action="store", dest="cronroot",
282                      default=None,
283                      help="pick root of cron dir")
284    parser.add_option("-d", "--development",
285                      action="store_true", dest="development",
286                      default=False,
287                      help="run in production")
288    parser.add_option("-r", "--cron-restart",
289                      action="store", dest="cron_restart",
290                      default=None,
291                      help="run in production")
292    (options, args) = parser.parse_args()
293    if len(args) < 1:
294        return usage(parser)
295    command = args[0]
296    args = args[1:]
297
298    if command == 'meta-data':
299        return meta_data_cron(args, options)
300    globals_status = _set_globals(args, options)
301    with lock('%s/hacron-%s.lock' % (HA_RSCTMP, OCF_RESOURCE_INSTANCE)):
302        if globals_status:
303            return globals_status
304        if command == 'start':
305            return start_cron(args, options)
306        elif command == 'reload':
307            return start_cron(args, options)
308        elif command == 'stop':
309            return stop_cron(args, options)
310        elif command == 'monitor':
311            return monitor_cron(args, options)
312        elif command == 'validate-all':
313            return validate_all_cron(args, options)
314        elif command == 'setup':
315            return setup(args, options)
316        elif command == 'add-servers':
317            return remove_servers(args, options)
318        else:
319            usage(parser)
320            return OCF_ERR_UNIMPLEMENTED
321
322if __name__ == '__main__':
323    try:
324        ret = main()
325    except Exception, e:
326        logger.error('exception from main: %s' % e)
327        ret = OCF_ERR_GENERIC
328        raise
329    sys.exit(ret)
Note: See TracBrowser for help on using the repository browser.