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

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